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:

$ mn create-app my-openapi-app --features swagger-java

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:

Adding the Swagger Dependencies
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:

Generated Swagger YAML
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:

Exposing Swagger YAML
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.

System Property
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:

Gradle
    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:

Gradle
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:

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:

Maven + 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 is swagger.

  • 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 is row.

  • rapidoc.theme=[dark | light]: The theme of rapidoc to use. These are case insensitive. Default is dark.

  • 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 is StandaloneLayout.

  • swagger-ui.theme=[DEFAULT | MATERIAL | FEELING_BLUE | FLATTOP | MONOKAI | MUTED | NEWSPAPER | OUTLINE]: The theme of swagger-ui to use. These are case insensitive. Default is DEFAULT.

  • swagger-ui.deep-linking=[true | false]: The deep-linking flag of swagger-ui to use. Default is true.

To expose the views, you also must expose the generated yaml:

Exposing Swagger YAML And Views
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:

Exposing Swagger YAML
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.

HttpServerFilter for context-path
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:

Example Generated Swagger Output
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:

Example Generated Swagger Output
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:

Gradle
tasks.withType(JavaCompile) {
    options.fork = true
    options.forkOptions.jvmArgs << '-Dmicronaut.openapi.property.naming.strategy=SNAKE_CASE'

    ...
}

or in maven:

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:

Using the @Schema Annotation
@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:

Swagger 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:

Swagger returns types and generics
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:

Changing the name of response schema
@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