OpenAPI/Swagger Support

Configuration to integrate Micronaut and OpenAPI/Swagger

Version:

1 Introduction

Micronaut includes support for producing OpenAPI (Swagger) YAML at compilation time. Micronaut will at compile time produce a OpenAPI 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.

If you wish to generate Micronaut projects from OpenAPI definition files, utilize the OpenAPI Generator's Micronaut support. Refer to the "Micronaut server genreation with OpenAPI" guide or the "Micronaut Client generation with OpenAPI" guide for details.

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Using the Micronaut CLI

To create a project with OpenAPI/Swagger support using the Micronaut CLI, supply the openapi feature to the features flag. For example:

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

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.

4 Dependencies

To get started add Micronaut’s openapi to the annotation processor scope of your build configuration:

annotationProcessor("io.micronaut.openapi:micronaut-openapi:4.5.2")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut.openapi</groupId>
        <artifactId>micronaut-openapi</artifactId>
        <version>4.5.2</version>
    </path>
</annotationProcessorPaths>

For Kotlin the openapi dependency should be in the kapt scope and for Groovy in the compileOnly scope.

To use the Swagger Annotations add them to the compile classpath

implementation("io.swagger.core.v3:swagger-annotations")
<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
</dependency>

5 OpenAPI Definition

Once dependencies have been configured a minimum requirement is to add a @OpenAPIDefinition annotation to your Application class:

Example @OpenAPIDefinition usage
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 = "https://foo.bar"),
                contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}
Example @OpenAPIDefinition usage
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 = "https://foo.bar"),
                contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
class Application {
    static void main(String[] args) {
        Micronaut.run(Application)
    }
}
Example @OpenAPIDefinition usage
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 = "https://foo.bar"),
                contact = Contact(url = "https://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 OpenAPI YAML file will be generated to the META-INF/swagger directory of your project’s class output. For example, the above configuration generates:

  • For Java build/classes/java/main/META-INF/swagger/hello-world-0.0.yml

  • For Kotlin build/tmp/kapt3/classes/main/META-INF/swagger/hello-world-0.0.yml

The previously defined annotations will produce YAML like the following:

Generated OpenAPI YAML
openapi: 3.0.1
info:
  title: the title
  description: My API
  contact:
    name: Fred
    url: https://gigantic-server.com
    email: Fred@gigagantic-server.com
  license:
    name: Apache 2.0
    url: https://foo.bar
  version: "0.0"

6 OpenAPI Processing Options

It is possible to tweak the OpenAPI processing with system properties or with a properties file. Options specified with system properties have priority over those defined in the openapi.properties file.

6.1 Configuring OpenAPI Processing with a properties file

You can specify OpenAPI processing configuration in a file located at the root level of your project directory. The expected filename is openapi.properties.

It is possible to specify a different location and filename with the micronaut.openapi.config.file System property.

openapi.properties Example
micronaut.openapi.property.naming.strategy=KEBAB_CASE
micronaut.openapi.target.file=myspecfile.yml
...
..
.

Properties prefixed with micronaut.openapi.expand will be expanded at compile time, for instance with:

openapi.properties Property Resolution
micronaut.openapi.expand.api.version=v1.1
micronaut.openapi.expand.openapi.description=A nice API

The following example shows how to use the previous micronaut.openapi.expand properties:

Application Simple Application
@OpenAPIDefinition(
        info = @Info(
                title = "Hello World",
                version = "${api.version}",
                description = "${openapi.description}",
                license = @License(name = "Apache 2.0", url = "https://foo.bar"),
                contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}

The generated specification file will look like:

Generated Swagger YAML
openapi: 3.0.1
info:
  title: Hello World
  description: A nice API
  contact:
    name: Fred
    url: https://gigantic-server.com
    email: Fred@gigagantic-server.com
  license:
    name: Apache 2.0
    url: https://foo.bar
  version: "v1.1"

6.2 Configuring OpenAPI Processing with system properties

It is possible to tweak the OpenAPI processing via system properties.

For instance in gradle:

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

    ...
}

or in gradle.properties

org.gradle.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 Exposing Swagger Output

If you wish to expose the generated OpenAPI yaml output from your running application you can simply add the necessary static resource configuration to src/main/resources/application.yml. For example:

Exposing OpenAPI 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.

8 OpenAPI Generation for Controllers

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:

Hello World Example
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import reactor.core.publisher.Mono;


@Controller("/")
public class HelloController {

    /**
     * @param name The person's name
     * @return The greeting
     */
    @Get(uri = "/hello/{name}", produces = MediaType.TEXT_PLAIN)
    public Mono<String> index(String name) {
        return Mono.just("Hello " + name + "!");
    }
}
Hello World Example
import io.micronaut.http.MediaType

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import reactor.core.publisher.Mono

@Controller("/")
class HelloController {

    /**
     * @param name The person's name
     * @return The greeting
     */
    @Get(uri = "/hello/{name}", produces = MediaType.TEXT_PLAIN)
    Mono<String> index(String name) {
        return Single.just("Hello $name!")
    }
}

@Controller("/")
class HelloController {

    /**
     * @param name The person's name
     * @return The greeting
     */
    @Get(uri = "/hello/{name}", produces = MediaType.TEXT_PLAIN)
    Mono<String> index(String name) {
        return Single.just("Hello $name!")
    }
}
Hello World Example
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import reactor.core.publisher.Mono


@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): Mono<String> {
        return Mono.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: https://gigantic-server.com
    email: Fred@gigagantic-server.com
  license:
    name: Apache 2.0
    url: https://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:
        200:
          description: The greeting
          content:
            text/plain:
              schema:
                type: string

Notice how the javadoc comments are used to fill out the description of the API.

9 Naming Strategy

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:

  • LOWER_CAMEL_CASE

  • UPPER_CAMEL_CASE

  • SNAKE_CASE

  • UPPER_SNAKE_CASE

  • LOWER_CASE

  • KEBAB_CASE

  • LOWER_DOT_CASE.

10 Swagger Annotations

You can take full control by augmenting your definition with Swagger Annotations. Swagger annotations take precedence over javadoc.

Add the Swagger annotations to the compile classpath

implementation("io.swagger.core.v3:swagger-annotations")
<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
</dependency>

and then annotate your controllers:

Using Swagger Annotations
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
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;
import reactor.core.publisher.Mono;


@Controller("/")
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 Mono<String> greetings(@Parameter(description="The name of the person") @NotBlank String name) {
        return Mono.just("Hello " + name + ", How are you doing?");
    }
}
Using Swagger Annotations
@Controller("/")
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")
    Mono<String> greetings(@Parameter(description="The name of the person") @NotBlank String name) {
        return Single.just("Hello $name, How are you doing?")
    }
}
Using Swagger Annotations
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import reactor.core.publisher.Mono
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("/")
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): Mono<String> {
        return Mono.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: https://gigantic-server.com
    email: Fred@gigagantic-server.com
  license:
    name: Apache 2.0
    url: https://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:
        200:
          content:
            text/plain:
              schema:
                type: string
        400:
          description: Invalid Name Supplied
        404:
          description: Person not found

10.1 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
import io.swagger.v3.oas.annotations.media.Schema;

@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.

10.2 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.

10.3 Schemas and Generics

If a method return type includes generics then these will be 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.4 Schemas naming

By default Micronaut uses Class simple name for Custom type schemas. Micronaut use simple name no matter if @Schema annotation is defined on type or on property (getter). That means that if you have two properties of same type with @Schema annotation without name set, Micronaut will accidentally override one definition with another. So in that case you should set name on @Schema annotation.

Schema name resolution
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description="A pet") (1)
class Pet {
}

class Owner {
    private Pet bird;
    private Pet cat;
    private Pet dog;


    @Schema(description="Pet that is a a bird") (2)
    public Pet getBird() {
        return bird;
    }

    @Schema(description="Pet that is a cat") (3)
    public Pet getCat() {
        return cat;
    }

    @Schema(name="Dog", description="Pet that is a dog") (4)
    public Pet getDog() {
        return cat;
    }
}
1 Micronaut will generate schema with name Pet
2 Micronaut will generate schema with name Pet since name is not set, this will conflict with <1> and <3>, final Pet schema might be incorrect
3 Micronaut will generate schema with name Pet since name is not set, this will conflict with <1> and <2>, final Pet schema might be incorrect
4 Micronaut will generate schema with unique name Dog since name is set, there is no conflict, schema is correctly generated

10.5 Schemas Annotation resolution

You can apply @Schema annotation to type or property. But it’s important to note, that Micronaut will prioritize @Schema on property over @Schema on type.

Schema annotation resolution
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description="Pet") (1)
class Pet {
}

class Owner {

    private Pet cat;
    private Pet dog;

    public Pet getCat() { (2)
        return cat;
    }

    @Schema(name="MyPet", description="This is my pet") (3)
    public Pet getDog() {
        return dog;
    }

}
1 Micronaut will detect this annotation
2 Micronaut will use annotation <1> from type since there is none on property
3 Micronaut will use this annotation even if there exists one on Pet type

11 Exposing Endpoints

It is possible to expose management Endpoints in the openapi specification file.

11.1 Enable Endpoints

To process user defined endpoints simply add:

openapi.properties
endpoints.enabled=true
...
..
.

11.2 Endpoints Tags

You can also provide some tags for all endpoints with the endpoints.tags=<comma separated list of tags> flag, for instance:

openapi.properties
endpoints.tags=Management Enpoints

11.3 Micronaut Built-In Endpoints

To enable the processing of built-in endpoints (https://docs.micronaut.io/latest/guide/index.html#providedEndpoints), you have to expose micronaut-management as annotation processor and runtime dependency:

annotationProcessor("io.micronaut:micronaut-management:4.5.2")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-management</artifactId>
        <version>4.5.2</version>
    </path>
</annotationProcessorPaths>

implementation("io.micronaut:micronaut-management:4.5.2")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-management</artifactId>
    <version>4.5.2</version>
</dependency>

and declare them in the openapi.properties file:

endpoints.enabled=true
endpoints.tags=Management Endpoints
endpoints.routes.class=io.micronaut.management.endpoint.routes.RoutesEndpoint
endpoints.beans.class=io.micronaut.management.endpoint.beans.BeansEndpoint
endpoints.health.class=io.micronaut.management.endpoint.health.HealthEndpoint
endpoints.loggers.class=io.micronaut.management.endpoint.loggers.LoggersEndpoint
endpoints.refresh.class=io.micronaut.management.endpoint.refresh.RefreshEndpoint

The syntax is the following: endpoints.<name>.class=<full class name of the endpoint> where name is an arbitrary name. You can also add some tags, servers and security requirements to each endpoint:

endpoints.refresh.class=io.micronaut.management.endpoint.refresh.RefreshEndpoint
endpoints.refresh.servers=[{"url": "https://staging.gigantic-server.com/v1", "description": "Staging server"}]
endpoints.refresh.security-requirements=[{"petstore_auth": ["write:pets", "read:pets"]}]

11.4 Endpoints Servers

You can also provide some servers for all endpoints with the endpoints.server=<json array of io.swagger.v3.oas.models.servers.Server> flag, for instance:

endpoints.servers=[ \
    { \
      "url": "https://{username}.gigantic-server.com:{port}/{basePath}", \
      "description": "The production API server", \
      "variables": { \
        "username": { \
          "default": "demo", \
          "description": "this value is assigned by the service provider, in this example `gigantic-server.com`" \
        }, \
        "port": { \
          "enum": [ \
            "8443", \
            "443" \
          ], \
          "default": "8443" \
        }, \
        "basePath": { \
          "default": "v2" \
        } \
      } \
    } \
  ]

11.5 Endpoints Security Requirements

You can also provide some security requirements for all endpoints with the endpoints.security-requirements=<json array of io.swagger.v3.oas.models.security.SecurityRequirement> flag, for instance:

openapi.properties endpoints.security-requirement property
endpoints.security-requirements=[{"api_key": []}]
...
..
.

Don’t forget to declare the referenced SecurityScheme.

11.6 Endpoints Path

If you are using a custom path for your endpoints use endpoints.path to set it:

openapi.properties endpoints.path property
endpoints.path=/endpoints
...
..
.

12 Annotation Configuration

Several annotations (OpenAPIInclude OpenAPISecurity OpenAPIManagement) are available to enhance the generated OpenAPI.

To use them add Micronaut’s openapi to the compile classpath of your application:

implementation("io.micronaut.openapi:micronaut-openapi:4.5.2")
<dependency>
    <groupId>io.micronaut.openapi</groupId>
    <artifactId>micronaut-openapi</artifactId>
    <version>4.5.2</version>
</dependency>

12.1 @OpenAPIInclude

You can use OpenAPIInclude you can include additional Controller or Endpoint already compiled classes in the OpenAPI processing.

@OpenAPIDefinition(
        info = @Info(
                title = "Hello World",
                version = "${api.version}",
                description = "${openapi.description}",
                license = @License(name = "Apache 2.0", url = "https://foo.bar"),
                contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
@OpenAPIInclude(
        classes = { io.micronaut.security.endpoints.LoginController.class, io.micronaut.security.endpoints.LogoutController.class },
        tags = @Tag(name = "Security")
)
@OpenAPIInclude(
        classes = io.micronaut.management.endpoint.env.EnvironmentEndpoint.class,
        tags = @Tag(name = "Management"),
        security = @SecurityRequirement(name = "BEARER", scopes = {"ADMIN"})
)
public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}

12.2 @OpenAPIManagement

OpenAPIManagement adds management endpoints.

OpenAPIManagement is mapped to:

@OpenAPIInclude(
classes = { io.micronaut.management.endpoint.beans.BeansEndpoint.class, io.micronaut.management.endpoint.env.EnvironmentEndpoint.class,
io.micronaut.management.endpoint.health.HealthEndpoint.class,
io.micronaut.management.endpoint.info.InfoEndpoint.class,
io.micronaut.management.endpoint.loggers.LoggersEndpoint.class,
io.micronaut.management.endpoint.refresh.RefreshEndpoint.class,
io.micronaut.management.endpoint.routes.RoutesEndpoint.class,
io.micronaut.management.endpoint.stop.ServerStopEndpoint.class,
io.micronaut.management.endpoint.threads.ThreadDumpEndpoint }
)

12.3 @OpenAPISecurity

OpenAPISecurity adds security endpoints.

It is mapped to:

@OpenAPIInclude(
classes = { io.micronaut.security.endpoints.LoginController.class, io.micronaut.security.endpoints.LogoutController.class }
)

12.4 @AccessorsStyle

You can use @AccessorsStyle to define your custom getters and setters if they are not the default get and set.

This is useful when defining getters and setters in a "fluent" way or when using Lombok for that:

@Introspected
@AccessorsStyle(readPrefixes = "", writePrefixes = "") (1)
class Person {

    private String name;
    private Integer debtValue;
    private Integer totalGoals;

    public Person(String name, Integer debtValue, Integer totalGoals) {
        this.name = name;
        this.debtValue = debtValue;
        this.totalGoals = totalGoals;
    }

    public String name() { (2)
        return name;
    }

    public Integer debtValue() {
        return debtValue;
    }

    public Integer totalGoals() {
        return totalGoals;
    }

    public void name(String name) { (2)
        this.name = name;
    }

    public void debtValue(Integer debtValue) {
        this.debtValue = debtValue;
    }

    public void totalGoals(Integer totalGoals) {
        this.totalGoals = totalGoals;
    }
}
1 Use @AccessorsStyle to configure the custom prefixes. In this case no prefix.
2 Define getters and setters without prefixes.

Using @AccessorsStyle will tell Micronaut how to access getters and setters and will also generate the appropriate Open API spec.

13 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.

For example, if you are using Micronaut’s Security OpenID Connect with Amazon Cognito your application exposes several endpoints which you could define in an external OpenAPI YAML file such as:

openapi/oauth.yml
openapi: 3.0.1
info:
  title: OAuth
  description: Endpoints related to the integration with Amazon Cognito
  version: "1.0"
paths:
  /logout:
    get:
      tags:
      - security
      description: deletes the JWT cookie and redirects to /oauth/login/cognito
      operationId: logout
      parameters: []
      responses:
        "200":
          description: logout 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        "302":
          links:
            oauth-login-cognito:
              operationId: oauth-login-cognito
  /oauth/callback/cognito:
    get:
      tags:
      - security
      description: receives a callback from the authorization server with a code to
        exchange it for an access and id token
      externalDocs:
        description: Amazon Cognito Token Endpoint documentation
        url: https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html
      operationId: oauth-callback-cognito
      parameters:
      - name: authorizationResponse
        in: query
        required: true
        explode: true
        schema:
          $ref: '#/components/schemas/AuthorizationResponse'
      responses:
        "303":
          description: redirects to home page upon successful completion of the authorization
            code grant flow
          headers:
            Set-Cookie:
              description: Cookied named JWT with the id token obtained from the authorization
                server as the value
  /oauth/login/cognito:
    get:
      tags:
      - security
      description: redirects to authorization server sign in page
      externalDocs:
        description: Amazon Cognito Authorization Endpoint documentation
        url: https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
      operationId: oauth-login-cognito
      parameters: []
      responses:
        "302":
          description: redirects to authorization server sign in page
  /oauth/logout:
    get:
      tags:
      - security
      description: ends the session in the authorization server and the redirects
        to /logout
      externalDocs:
        description: Amazon Cognito Logout Endpoint documentation
        url: https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html
      operationId: oauth-logout
      parameters: []
      responses:
        "302":
          links:
            logout:
              operationId: logout
components:
  schemas:
    AuthorizationResponse:
      required:
      - code
      - state
      type: object
      properties:
        code:
          required:
          - "true"
          type: string
          description: an authorization code which the OAuth 2.0 client can exchange
            for an access token
        nonce:
          required:
          - "false"
          type: string
          nullable: true
        state:
          required:
          - "true"
          type: string

You could also have a yaml file describing the endpoint which exposes the generated OpenAPI YAML file:

openapi/swagger.yml
openapi: 3.0.1
info:
  title: swagger
  version: "1.0"
paths:
  /swagger/demo-0.0.yml:
    get:
      tags:
        - openapi
      description: returns the OpenAPI YAML file describing the API
      operationId: swagger
      parameters: []
      responses:
        "200":
          description: OpenAPI YAML file describing the API
          content:
            text/plain: {}

To merge both files with the generated OpenAPI definition point Micronaut to look for additional OpenAPI yaml files in the openapi folder. You need to set the property micronaut.openapi.additional.files.

openapi.properties
micronaut.openapi.additional.files=openapi
...
..
.

Micronaut will include the endpoints defined in those files in the generated output.

14 Generating OpenAPI Views

Micronaut can generate views for your generated OpenApi specification. Currently Swagger-ui, Redoc and RapiDoc are supported. You can also use RapiPdf to generate a PDF from your spec file.

You can enable multiple views generation in a single application.

The resources needed to render the views (javascript, css, …​) are loaded from CDNs: unpkg.com and fonts.googleapis.com.

14.1 Mapping Path

The path from where the swagger specification will be served by the http server defaults to swagger. You can change it via the mapping.path property.

Thus, 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…​.

14.2 Enable Views Generation with a properties file

By default the generation of views is disabled. You can enable views generation with a configuration properties file.

openapi.properties Example Views Generation Swagger-UI Redoc, Rapidoc
swagger-ui.enabled=true
redoc.enabled=true
rapidoc.enabled=true
rapidoc.bg-color=#14191f
rapidoc.text-color=#aec2e0
rapidoc.sort-endpoints-by=method
...
..
.

14.3 Enable Views Generation with system properties

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.properties:

org.gradle.jvmargs=-Dmicronaut.openapi.views.spec=redoc.enabled=true,rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop

or in build.gradle as well:

kapt {
    arguments {
        arg("micronaut.openapi.views.spec", "redoc.enabled=true,rapidoc.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop")
    }
}

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 Gradle for Groovy projects:

Gradle
tasks.withType(GroovyCompile) {
    groovyOptions.forkOptions.jvmArgs.add('-Dgroovy.parameters=true')
    groovyOptions.forkOptions.jvmArgs.add('-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>-Amicronaut.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>

14.4 Swagger-UI

Views supports Swagger UI, to enable it use swagger-ui.enabled=true.

The views will be generated to the META-INF/swagger/views/swagger-ui directory of your project’s class output.

swagger-ui.enabled

true or false When 'true' the Swagger-ui view is generated.

swagger-ui.version

String - The version of Swagger-ui to use. Default is to use the latest available.

swagger-ui.theme

DEFAULT or MATERIAL or FEELING_BLUE FLATTOP MONOKAI MUTED NEWSPAPER OUTLINE. The theme of swagger-ui to use. These are case insensitive. Default is DEFAULT. See Swagger UI Themes.

swagger-ui.displayOperationId

swagger-ui.oauth2RedirectUrl

swagger-ui.showMutatedRequest

swagger-ui.deepLinking

swagger-ui.supportedSubmitMethods

swagger-ui.defaultModelsExpandDepth

swagger-ui.layout

swagger-ui.defaultModelRendering

swagger-ui.docExpansion

swagger-ui.filter

swagger-ui.validatorUrl

swagger-ui.showCommonExtensions

swagger-ui.maxDisplayedTags

swagger-ui.withCredentials

swagger-ui.displayRequestDuration

swagger-ui.showExtensions

swagger-ui.operationsSorter

swagger-ui.tagsSorter

See Swagger UI Configuration for a description.

To expose the the swagger-ui views, you also must expose the generated yaml:

Exposing Swagger YAML and Swagger UI Views
micronaut:
    router:
        static-resources:
            swagger:
                paths: classpath:META-INF/swagger
                mapping: /swagger/**
            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/swagger-ui.

14.4.1 Swagger UI - OAuth 2.0 configuration

Swagger UI OAuth 2.0 integration allows you to obtain a token from an authorization server directly from Swagger UI. Then, when you use the Try This button in the Swagger UI, the requests issued incorporate a valid token.

You can configure it by setting the following properties:

swagger-ui.oauth2RedirectUrl

swagger-ui.oauth2.clientId

swagger-ui.oauth2.clientSecret

swagger-ui.oauth2.realm

swagger-ui.oauth2.appName

swagger-ui.oauth2.scopeSeparator

swagger-ui.oauth2.scopes

swagger-ui.oauth2.additionalQueryStringParams

swagger-ui.oauth2.useBasicAuthenticationWithAccessCodeGrant

swagger-ui.oauth2.usePkceWithAuthorizationCodeGrant

When setting any of those properties, Micronaut will generate not only a swagger-ui/index.html file, but also a swagger-ui/oauth2-redirect.html one. You will need to configure that endpoint as a callback URL in your OAuth 2 Authorization Server.

An example configuration could be:

micronaut.openapi.views.spec=swagger-ui.enabled=true,swagger-ui.theme=flattop,swagger-ui.oauth2RedirectUrl=http://localhost:8080/swagger-ui/oauth2-redirect.html,swagger-ui.oauth2.clientId=myClientId,swagger-ui.oauth2.scopes=openid,swagger-ui.oauth2.usePkceWithAuthorizationCodeGrant=true

Then, you configure the @SecurityScheme:

@SecurityScheme(name = "openid",
        type = SecuritySchemeType.OAUTH2,
        scheme = "bearer",
        bearerFormat = "jwt",
        flows = @OAuthFlows(
                authorizationCode = @OAuthFlow(
                        authorizationUrl = "https://mycompany.okta.com/oauth2/default/v1/authorize",
                        tokenUrl = "https://mycompany.okta.com/oauth2/default/v1/token",
                        refreshUrl = "",
                        scopes = @OAuthScope(name = "openid", description = "OpenID role")
                )
        )
)
@OpenAPIDefinition(
        info = @Info(
                title = "API service",
                version = "0.0",
                description = "My API",
                license = @License(name = "Apache 2.0", url = "https://foo.bar"),
                contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
public class Application {
    public static void main(String[] args) {
        Micronaut.run(Application.class, args);
    }
}

And the appropriate @SecurityRequirement on controllers,. eg:

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;

@Controller
@Secured(SecurityRule.IS_AUTHENTICATED)
public class OrderController {

    @Get
    @SecurityRequirement(name = "openid", scopes = "openid")
    public String index() {
        return "Example Response";
    }
}

Do not forget to configure Micronaut Security accordingly:

application.yml
micronaut:
  security:
    enabled: true
    token:
      jwt:
        enabled: true
        signatures:
          jwks:
            okta:
              url: 'https://mycompany.okta.com/oauth2/default/v1/keys'
    intercept-url-map:
      - pattern: /swagger-ui/**
        httpMethod: GET
        access:
          - isAnonymous()
      - pattern: /swagger/**
        access:
          - isAnonymous()
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      swagger-ui:
        paths: classpath:META-INF/swagger/views/swagger-ui
        mapping: /swagger-ui/**

The previous sample uses a remote Json Web Key Set to validate the token issued by the authorization server. If you use Micronaut Security OpenID Connect support with a server compatible with OpenID Connect Discovery, the JWKS of the authorization server is automatically configured.

14.5 Redoc

Views supports Redoc, to enable it use redoc.enabled=true.

The views will be generated to the META-INF/swagger/views/redoc directory of your project’s class output.

redoc.enabled

true or false When 'true' the Redoc view is generated.

redoc.version

String - The version of Redoc to use. Default is to use the latest available.

redoc.expand-single-schema-field

redoc.expand-default-server-variables

redoc.menu-toggle

redoc.only-required-in-samples

redoc.payload-sample-idx

redoc.sort-props-alphabetically

redoc.untrusted-spec

redoc.expand-responses

redoc.show-extensions

redoc.native-scrollbars

redoc.path-in-middle-panel

redoc.suppress-warnings

redoc.hide-hostname

redoc.disable-search

redoc.json-sample-expand-level

redoc.scroll-y-offset

redoc.hide-download-button

redoc.no-auto-auth

redoc.theme

redoc.hide-single-request-sample-tab

redoc.required-props-first

redoc.hide-loading

See Redoc Options for a description of the above properites.

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

Exposing Swagger YAML And Redoc Views
micronaut:
    router:
        static-resources:
            swagger:
                paths: classpath:META-INF/swagger
                mapping: /swagger/**
            redoc:
                paths: classpath:META-INF/swagger/views/redoc
                mapping: /redoc/**

With the above configuration in place when you run your application you can access your Swagger documentation at http://localhost:8080/redoc.

14.6 RapiDoc

The views will be generated to the META-INF/swagger/views/rapidoc directory of your project’s class output.

rapidoc.enabled

true or false When 'true' the RapiDoc view is generated.

rapidoc.version

The version of RapiDoc to use. Default is to use the latest available.

rapidoc.style

rapidoc.sort-tags

rapidoc.sort-endpoints-by

rapidoc.heading-text

rapidoc.goto-path

rapidoc.theme

rapidoc.bg-color

rapidoc.text-color

rapidoc.header-color

rapidoc.regular-font

rapidoc.mono-font

rapidoc.font-size

rapidoc.nav-bg-color

rapidoc.nav-text-color

rapidoc.nav-hover-bg-color

rapidoc.nav-hover-text-color

rapidoc.nav-accent-color

rapidoc.nav-item-spacing

rapidoc.layout

rapidoc.render-style

rapidoc.schema-style

rapidoc.schema-expand-level

rapidoc.schema-description-expanded

rapidoc.default-schema-tab

rapidoc.response-area-height

rapidoc.show-info

rapidoc.info-description-headings-in-navbar

rapidoc.show-components

rapidoc.show-header

rapidoc.allow-authentication

rapidoc.allow-spec-url-load

rapidoc.allow-spec-file-load

rapidoc.allow-search

rapidoc.allow-try

rapidoc.allow-server-selection

rapidoc.api-key-name

rapidoc.api-key-value

rapidoc.api-key-location

rapidoc.server-url

rapidoc.default-api-server

See RapiDoc Options for a description.

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

Exposing Swagger YAML And Rapidoc Views
micronaut:
    router:
        static-resources:
            swagger:
                paths: classpath:META-INF/swagger
                mapping: /swagger/**
            rapidoc:
                paths: classpath:META-INF/swagger/views/rapidoc
                mapping: /rapidoc/**

With the above configuration in place when you run your application you can access your Swagger documentation at http://localhost:8080/rapidoc.

14.7 RapiPdf

Views also supports RapiPdf, to enable it use rapipdf.enabled=true.

RapiPdf supports the following options:

rapipdf.enabled

true or false

rapipdf.include-api-details

rapipdf.pdf-title

rapipdf.include-api-list

rapipdf.include-security

rapipdf.input-bg

rapipdf.hide-input

rapipdf.pdf-footer-text

rapipdf.pdf-primary-color

rapipdf.pdf-schema-style

rapipdf.button-label

rapipdf.pdf-alternate-color

rapipdf.include-info

rapipdf.include-toc

rapipdf.button-color

rapipdf.style

rapipdf.input-color

See RapiPdf Attributes for a description.

It will add a button to the view to generate a PDF from the spec file.

15 Server Context

In the 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 changes during runtime (e.g. context-path is determined by a reverse proxy).

It is still possible for the views to work in case a context path is defined:

  • Set micronaut.openapi.server.context.path property for compile time resolution, or

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

15.1 Compile Time Resolution

Either set micronaut.openapi.server.context.path as a System Property or in openapi.properties, then all paths will be prepend with the specified value at compile time.

If you want the resolution of the context path at runtime use one of the following methods:

15.2 HttpServerFilter

Use a HttpServerFilter to add a cookie which contains the context-path. This can be done in two ways:

  • Set the context-path from a static property (has to be set during compile time), or

  • Parse the context path from the request headers. This is particularly useful if your application runs behind a reverse proxy, which strips the context-path before forwarding the request to the application. Most reverse proxies should provide the possibility to set the stripped context-path as a header (e.g. X-Forwarded-Prefix in the case of traefik).

Static Property

Create a HttpServerFilter that will add a cookie with name contextPath.

HttpServerFilter for static context-path
import java.time.Duration;

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", contextPath).maxAge(Duration.ofMinutes(2L));
    }

    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        return Publishers.map(chain.proceed(request), response -> response.cookie(contextPathCookie));
    }

}

From HTTP Header

The HttpServerFilter looks very similar to the one above. The main difference is that it parses the context-path value from the request headers.

HttpServerFilter from request headers
import java.time.Duration;

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;


@Filter(
	methods = {HttpMethod.GET, HttpMethod.HEAD},
	patterns = {"/**/rapidoc*", "/**/redoc*", "/**/swagger-ui*"}
)
@Requires(property = "micronaut.server.context-path-header")
public class OpenApiContextPathFilter implements HttpServerFilter {

	private final String contextPathHeader;

	OpenApiContextPathFilter(@Value("${micronaut.server.context-path-header}") String contextPathHeader) {
		this.contextPathHeader = contextPathHeader;
	}

	@Override
	public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
		final String contextPath = request.getHeaders().get(contextPathHeader);

		if (contextPath != null) {
			Cookie contextPathCookie = Cookie.of("contextPath", contextPath).maxAge(Duration.ofMinutes(2L));
			return Publishers.map(chain.proceed(request), response -> response.cookie(contextPathCookie));
		} else {
			return chain.proceed(request);
		}
	}

}

15.3 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.

16 Repository

You can find the source code of this project in this repository:

17 Breaking Changes

This section documents breaking changes between Micronaut OpenAPI versions:

Micronaut OpenAPI 4.0.0

Micronaut OpenAPI no longer generates 200 or default HTTP status code responses when using @ApiResponse annotation. It’s up to the user to define all the appropriate status codes.