$ mn create-app my-openapi-app --features swagger-java
OpenAPI/Swagger Support
Configuration to integrate Micronaut and OpenAPI/Swagger
Version: 1.3.4
1 Introduction
Micronaut includes support for producing OpenAPI (Swagger) YAML at compilation time. Micronaut will at compile time produce a Swagger 3.x compliant YAML file just based on the regular Micronaut annotations and the javadoc comments within your code.
You can customize the generated Swagger using the standard Swagger Annotations.
2 Using the Micronaut CLI
To create a project with OpenAPI/Swagger support using the Micronaut CLI, supply the swagger-*
feature to the features
flag. For example:
This will create a project with the minimum necessary configuration for OpenAPI.
If you have already created a Micronaut project and will like to add Swagger support, you can simply follow instructions in subsequent sections.
3 Getting Started with Swagger
To enable this support you should add the following dependencies to your build configuration:
annotationProcessor "io.micronaut.configuration:micronaut-openapi" (1)
compile "io.swagger.core.v3:swagger-annotations" (2)
1 | The openapi configuration gets added into the annotation processor scope |
2 | The Swagger Annotations are added to the compile classpath |
For Kotlin the openapi dependency should be in the kapt scope and for Groovy in the compileOnly scope.
|
Once dependencies have been configured a minimum requirement is to add a @OpenAPIDefinition
annotation to your Application
class:
import io.micronaut.runtime.Micronaut;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
@OpenAPIDefinition(
info = @Info(
title = "Hello World",
version = "0.0",
description = "My API",
license = @License(name = "Apache 2.0", url = "http://foo.bar"),
contact = @Contact(url = "http://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
)
)
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class);
}
}
import io.micronaut.runtime.Micronaut
import groovy.transform.CompileStatic
import io.swagger.v3.oas.annotations.*
import io.swagger.v3.oas.annotations.info.*
@OpenAPIDefinition(
info = @Info(
title = "Hello World",
version = "0.0",
description = "My API",
license = @License(name = "Apache 2.0", url = "http://foo.bar"),
contact = @Contact(url = "http://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
)
)
@CompileStatic
class Application {
static void main(String[] args) {
Micronaut.run(Application)
}
}
import io.micronaut.runtime.Micronaut
import io.swagger.v3.oas.annotations.OpenAPIDefinition
import io.swagger.v3.oas.annotations.info.Contact
import io.swagger.v3.oas.annotations.info.Info
import io.swagger.v3.oas.annotations.info.License
@OpenAPIDefinition(
info = Info(
title = "Hello World",
version = "0.0",
description = "My API",
license = License(name = "Apache 2.0", url = "http://foo.bar"),
contact = Contact(url = "http://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
)
)
object Application {
@JvmStatic
fun main(args: Array<String>) {
Micronaut.run(Application.javaClass)
}
}
With that in place you compile your project and a Swagger YAML file will be generated to the META-INF/swagger
directory of your project’s class output. For example the above configuration for Java will be generated to build/classes/java/main/META-INF/swagger/hello-world-0.0.yml
and build/tmp/kapt3/classes/main/META-INF/swagger/hello-world-0.0.yml
for Kotlin.
The previously defined annotations will produce YAML like the following:
openapi: 3.0.1
info:
title: the title
description: My API
contact:
name: Fred
url: http://gigantic-server.com
email: Fred@gigagantic-server.com
license:
name: Apache 2.0
url: http://foo.bar
version: "0.0"
4 Exposing Swagger Output
If you wish to expose the generated Swagger output from your running application you can simply add the necessary static resource configuration to src/main/resources/application.yml. For example:
micronaut:
router:
static-resources:
swagger:
paths: classpath:META-INF/swagger
mapping: /swagger/**
With the above configuration in place when you run your application you can access your Swagger documentation at http://localhost:8080/swagger/hello-world-0.0.yml
.
5 Generating OpenAPI Views
Micronaut can generate views for your generated OpenApi specification. Currently swagger-ui, redoc and rapidoc are supported.
The resources needed to render the views (javascript, css, …) are loaded from CDNs: unpkg.com and fonts.googleapis.com.
By default the generation of views is disabled. To turn it on you have to set the following system property micronaut.openapi.views.spec
.
The string syntax is a series of comma-separated key-value pairs, to enable and configure the views.
micronaut.openapi.views.spec=redoc.enabled=true,rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop
For instance in Gradle for Kotlin projects:
JAVA_TOOL_OPTIONS=-Dmicronaut.openapi.views.spec=redoc.enabled=true,rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop \
./gradlew --no-daemon clean assemble
or in Gradle for Java projects:
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs << '-Dmicronaut.openapi.views.spec=rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop'
...
}
or in Maven:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<compilerArgs>
<arg>-J-Dmicronaut.openapi.views.spec=rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop</arg>
...
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
or in Maven with Groovy:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>set-system-properties</goal>
</goals>
<configuration>
<properties>
<property>
<name>micronaut.openapi.views.spec</name>
<value>rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop</value>
</property>
</properties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
The views will be generated to the META-INF/swagger/views/[swagger-ui | redoc | rapidoc]
directory of your project’s class output.
The following properties are supported:
-
mapping.path=[string]
: The path from where the swagger specification will be served by the http server. Default isswagger
. -
redoc.enabled=[true | false]
: When 'true' the redoc view is generated. -
rapidoc.enabled=[true | false]
: When 'true' the rapidoc view is generated. -
swagger-ui.enabled=[true | false]
: When 'true' the swagger-ui view is generated. -
redoc.version=[string]
: The version of redoc to use. Default is to use the latest available. -
rapidoc.version=[string]
: The version of rapidoc to use. Default is to use the latest available. -
rapidoc.layout=[row | column]
: The layout of rapidoc to use. These are case insensitive. Default isrow
. -
rapidoc.theme=[dark | light]
: The theme of rapidoc to use. These are case insensitive. Default isdark
. -
swagger-ui.version=[string]
: The version of swagger-ui to use. Default is to use the latest available. -
swagger-ui.layout=[string]
: The layout of swagger-ui to use. Defaults isStandaloneLayout
. -
swagger-ui.theme=[DEFAULT | MATERIAL | FEELING_BLUE | FLATTOP | MONOKAI | MUTED | NEWSPAPER | OUTLINE]
: The theme of swagger-ui to use. These are case insensitive. Default isDEFAULT
. -
swagger-ui.deep-linking=[true | false]
: The deep-linking flag of swagger-ui to use. Default istrue
.
To expose the views, you also must expose the generated yaml
:
micronaut:
router:
static-resources:
swagger:
paths: classpath:META-INF/swagger
mapping: /swagger/**
redoc:
paths: classpath:META-INF/swagger/views/redoc
mapping: /redoc/**
rapidoc:
paths: classpath:META-INF/swagger/views/rapidoc
mapping: /rapidoc/**
swagger-ui:
paths: classpath:META-INF/swagger/views/swagger-ui
mapping: /swagger-ui/**
With the above configuration in place when you run your application you can access your Swagger documentation at http://localhost:8080/[redoc|rapidoc|swagger-ui].
By default the views expect to find the yaml
under /swagger
, if you change this mapping to something else:
micronaut:
router:
static-resources:
swagger:
paths: classpath:META-INF/swagger
mapping: /swaggerYAML/**
....
You will need to set the mapping.path
property accordingly: micronaut.openapi.views.spec=mapping.path=swaggerYAML…
.
Server Context Path
In micronaut configuration file you can define a server context path (with micronaut.server.context-path
) which serves as a base path for all routes.
Since the yaml specification file and the views are generated at compile time, these resources are not aware of this runtime setting.
It is still possible for the views to work in case a context path is defined:
* Use a HttpServerFilter
that will add a cookie, or
* Add a parameter to the url.
The view will first look for the cookie and if not present for the parameter.
HttpServerFilter
Create a HttpServerFilter
that will add a cookie with name contextPath
.
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.reactivestreams.Publisher;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.annotation.Value;
import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.cookie.Cookie;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
@Requires(property = "micronaut.server.context-path")
@Filter(methods = {HttpMethod.GET, HttpMethod.HEAD}, patterns = {"/**/rapidoc*", "/**/redoc*", "/**/swagger-ui*"})
public class OpenApiViewCookieContextPathFilter implements HttpServerFilter {
private final Cookie contextPathCookie;
OpenApiViewCookieContextPathFilter(@Value("${micronaut.server.context-path}") String contextPath) {
this.contextPathCookie = Cookie.of("contextPath", URLEncoder.encode(contextPath, StandardCharsets.UTF_8) + ";max-age=2;samesite");
}
@Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
return Publishers.map(chain.proceed(request), response -> response.cookie(contextPathCookie));
}
}
URL Parameter
Just add a parameter to the view url. For instance if the context path is set to /context/path
you will access your view with http://localhost:8080/context/path/swagger-ui?contextPath=%2Fcontext%2Fpath
.
6 Controllers and Swagger Annotations
By default Micronaut will automatically at compile time build out the Swagger YAML definition from your defined controllers and methods. For example given the following class:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;
import io.reactivex.Single;
@Validated
@Controller("/")
public class HelloController {
/**
* @param name The person's name
* @return The greeting
*/
@Get(uri = "/hello/{name}", produces = MediaType.TEXT_PLAIN)
public Single<String> index(String name) {
return Single.just("Hello " + name + "!");
}
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import io.reactivex.Single
@Validated
@Controller("/")
class HelloController {
/**
* @param name The person's name
* @return The greeting
*/
@Get(uri = "/hello/{name}", produces = MediaType.TEXT_PLAIN)
Single<String> index(String name) {
return Single.just("Hello $name!")
}
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import io.reactivex.Single
@Validated
@Controller("/")
open class HelloController {
/**
* @param name The person's name
* @return The greeting
*/
@Get(uri = "/hello/{name}", produces = [MediaType.TEXT_PLAIN])
open fun index(name: String): Single<String> {
return Single.just("Hello $name!")
}
}
The resulting output will be:
openapi: 3.0.1
info:
title: Hello World
description: My API
contact:
name: Fred
url: http://gigantic-server.com
email: Fred@gigagantic-server.com
license:
name: Apache 2.0
url: http://foo.bar
version: "0.0"
paths:
/hello/{name}:
get:
description: ""
operationId: index
parameters:
- name: name
in: path
description: The person's name
required: true
schema:
type: string
responses:
default:
description: The greeting
content:
text/plain:
schema:
type: string
Notice how the javadoc comments are used to fill out the description of the API. If this is not desirable then you can take full control by augmenting your definition with Swagger annotations:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;
import io.reactivex.Single;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.validation.constraints.NotBlank;
@Controller("/")
@Validated
public class HelloController {
/**
* @param name The person's name
* @return The greeting message
*/
@Get(uri="/greetings/{name}", produces= MediaType.TEXT_PLAIN)
@Operation(summary = "Greets a person",
description = "A friendly greeting is returned"
)
@ApiResponse(
content = @Content(mediaType = "text/plain",
schema = @Schema(type="string"))
)
@ApiResponse(responseCode = "400", description = "Invalid Name Supplied")
@ApiResponse(responseCode = "404", description = "Person not found")
@Tag(name = "greeting")
public Single<String> greetings(@Parameter(description="The name of the person") @NotBlank String name) {
return Single.just("Hello " + name + ", How are you doing?");
}
}
@Controller("/")
@Validated
class HelloController {
/**
* @param name The person's name
* @return The greeting message
*/
@Get(uri="/greetings/{name}", produces= MediaType.TEXT_PLAIN)
@Operation(summary = "Greets a person",
description = "A friendly greeting is returned"
)
@ApiResponse(
content = @Content(mediaType = "text/plain",
schema = @Schema(type="string"))
)
@ApiResponse(responseCode = "400", description = "Invalid Name Supplied")
@ApiResponse(responseCode = "404", description = "Person not found")
@Tag(name = "greeting")
Single<String> greetings(@Parameter(description="The name of the person") @NotBlank String name) {
return Single.just("Hello $name, How are you doing?")
}
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import io.reactivex.Single
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
@Controller("/")
@Validated
open class HelloController {
/**
* @param name The person's name
* @return The greeting message
*/
@Get(uri = "/greetings/{name}", produces = [MediaType.TEXT_PLAIN])
@Operation(summary = "Greets a person", description = "A friendly greeting is returned")
// Please Note: Repeatable Annotations with non-SOURCE retentions are not yet supported with Kotlin so we are using `@ApiResponses`
// instead of `@ApiResponse`, see https://youtrack.jetbrains.com/issue/KT-12794
@ApiResponses(
ApiResponse(content = [Content(mediaType = "text/plain", schema = Schema(type = "string"))]),
ApiResponse(responseCode = "400", description = "Invalid Name Supplied"),
ApiResponse(responseCode = "404", description = "Person not found")
)
@Tag(name = "greeting")
open fun greetings(name: String): Single<String> {
return Single.just("Hello $name, how are you doing?")
}
}
The resulting output will be:
openapi: 3.0.1
info:
title: Hello World
description: My API
contact:
name: Fred
url: http://gigantic-server.com
email: Fred@gigagantic-server.com
license:
name: Apache 2.0
url: http://foo.bar
version: "0.0"
paths:
/greetings/{name}:
get:
tags:
- greeting
summary: Greets a person
description: A friendly greeting is returned
operationId: greetings
parameters:
- name: name
in: path
description: The name of the person
required: true
schema:
minLength: 1
type: string
responses:
default:
content:
text/plain:
schema:
type: string
400:
description: Invalid Name Supplied
404:
description: Person not found
You can control how the Schema property names are dumped by setting the micronaut.openapi.property.naming.strategy
system property. It accepts one of
the following jackson's PropertyNamingStrategy
:
- SNAKE_CASE,
- UPPER_CAMEL_CASE,
- LOWER_CAMEL_CASE,
- LOWER_CASE and
- KEBAB_CASE.
For instance in gradle:
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs << '-Dmicronaut.openapi.property.naming.strategy=SNAKE_CASE'
...
}
or in maven:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<compilerArgs>
<arg>-J-Dmicronaut.openapi.property.naming.strategy=SNAKE_CASE</arg>
...
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
7 Schemas and POJOs
If you return types are not simple strings and primitive types then Micronaut will attempt to generate a Schema definition. You can customize the
generation of the Schema by using the @Schema
annotation on your POJO. For example:
@Schema(name="MyPet", description="Pet description") (1)
class Pet {
private PetType type;
private int age;
private String name;
public void setAge(int a) {
age = a;
}
/**
* The age
*/
@Schema(description="Pet age", maximum="20") (2)
public int getAge() {
return age;
}
public void setName(String n) {
name = n;
}
@Schema(description="Pet name", maxLength=20)
public String getName() {
return name;
}
public void setType(PetType t) {
type = t;
}
public PetType getType() {
return type;
}
}
enum PetType {
DOG, CAT;
}
1 | The @Schema annotation is used to customize the name of the schema |
2 | Properties can be customized too. |
8 Schemas and Meta Annotations
If you don’t have control of the source code and don’t want to have to annotate each parameter with @Schema
then it can be convenient to instead use a meta annotation.
For example if the aforementioned Pet
class cannot be annotated with @Schema
you can define a meta annotation:
@Documented
@Retention(RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Schema(name="MyPet", description="Pet description")
@interface MyAnn {
}
Then whenever Pet
is used as a parameter you can annotate the parameter with @MyAnn
.
9 Schemas and Generics
If a method return type includes generics then these will included when calculating the schema name. For example the following:
class Response<T> {
private T r;
public T getResult() {
return r;
};
}
@Controller("/")
class MyController {
@Put("/")
public Response<Pet> updatePet(Pet pet) {
...
}
}
Will result in a schema called #/components/schemas/Response<Pet>
being generated. If you wish to alter the name of the schema you can do so with the @Schema
annotation:
@Put("/")
@Schema(name="ResponseOfPet")
public Response<Pet> updatePet(Pet pet) {
...
}
In the above case the generated schema will be named #/components/schemas/ResponseOfPet
.
10 Merging Schemas
Often times you might want to generate OpenAPI (Swagger) YAML for built-in endpoints or paths from some other modules, such as security
. In order to generate YAML including all that information, Micronaut supports merging of multiple OpenAPI YAML files. So, you can create OpenAPI YAML files manually at some predefined path from where the information will then be merged into the final YAML file.
To configure the path for additional swagger files you need to set System
property micronaut.openapi.additional.files
. For example:
micronaut.openapi.additional.files={project.home}/src/test/resources/swagger