Table of Contents

OpenAPI/Swagger Support

Configuration to integrate Micronaut and OpenAPI/Swagger

Version: 6.12.3

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 generation 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")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut.openapi</groupId>
        <artifactId>micronaut-openapi</artifactId>
    </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 or Micronaut OpenAPI annotations add them to compile classpath

compileOnly("io.micronaut.openapi:micronaut-openapi-annotations")
<dependency>
    <groupId>io.micronaut.openapi</groupId>
    <artifactId>micronaut-openapi-annotations</artifactId>
    <scope>provided</scope>
</dependency>

Also, do not forget that for the correct operation of the annotation processor, the correct parameter names in the controllers are required, therefore it is recommended that all libraries from which you plan to add controllers be compiled with the -parameters flag. For example like this (with gradle build):
tasks.withType(JavaCompile).configureEach {
    options.compilerArgs = [
            '-parameters'
    ]
}

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"
So, micronaut-openapi expanded properties is old solution, now you can also use any properties for placeholders. For example, if you set properties without prefix micronaut.openapi.expand micronaut-openapi will process them as with this prefix.
openapi.properties Property Resolution without prefix
api.version=v1.1
openapi.description=A nice API

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>

6.3 Configuring OpenAPI Processing with an application.yml file

It is possible to tweak the OpenAPI processing via standard way with micronaut environments (application.yml file).

application.yml Example
micronaut:
  openapi:
    target:
     file: myspecfile.yml
    property:
      naming:
        strategy: KEBAB_CASE

Also, you can use properties from application.yml file for placeholders.

application.yml Sample properties for placeholders
my:
  api:
    version: 1.0.0
    title: My title
    api-description: My description
Application.java Simple Application
@OpenAPIDefinition(
        info = @Info(
                title = "${my.api.version}",
                version = "${my.api.title}",
                description = "${my.api.api-description}"
        )
)
public class Application {

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

6.4 Available OpenAPI processing options

micronaut.openapi.enabled

System property that enables or disables open api annotation processing.

Default: true

micronaut.openapi.openapi31.enabled

System property that enables or disables OpenAPI 3.1.0 format.

Default: false

micronaut.openapi.openapi31.json-schema-dialect

System property that set JSON Schema Dialect for OpenAPI 3.1.0 format.

Default: ``

micronaut.openapi.swagger.file.generation.enabled

System property that enables or disables generation of a swagger (OpenAPI) specification file. This can be used whenever you already have a specification file and that you only need the Swagger UI.

Default: true

micronaut.openapi.config.file

System property that enables setting the open api config file.

micronaut.openapi.server.context.path

System property for server context path.

micronaut.openapi.property.naming.strategy

System property for naming strategy. One jackson PropertyNamingStrategy.

Default: LOWER_CAMEL_CASE

micronaut.openapi.views.spec

System property for views specification.

micronaut.openapi.target.file

System property that enables setting the target file to write to.

Default: META-INF/swagger/${applicationName}-$6.12.3.yml

micronaut.openapi.views.dest.dir

System property that specifies the path where the generated UI elements will be located.

Default: META-INF/swagger/views/

micronaut.openapi.additional.files

System property that specifies the location of additional swagger YAML and JSON files to read from.

micronaut.openapi.project.dir

System property that specifies the location of current project. Need to set this property manually if you use kotlin (to save incremental annotation processing) or have some troubles with auto calculation project path.

Default: calculated automatically

micronaut.openapi.security.default-schema-name

System property that specifies the default security schema name, if it’s not specified by annotation SecurityScheme.

micronaut.openapi.security.enabled

If this property true, micronaut-openapi will process micronaut-security properties and annotations to construct openapi security schema.

Default: true

micronaut.openapi.field.visibility.level

System property that specifies the schema classes fields visibility level. By default, only public fields visible.
 
Available values:
PRIVATE
PACKAGE
PROTECTED
PUBLIC

Default: PUBLIC

micronaut.openapi.json.format

Is this property true, output file format will be JSON, otherwise YAML.

Default: false

micronaut.openapi.filename

The name of the result swagger file.

Default: ${info.title}-${info.version}.yml, if info block not set, filename will be swagger.yml.

micronaut.openapi.environments

Active micronaut environments which will be used for @Requires annotations.

micronaut.openapi.environments.enabled

Is this property true, properties wll be loaded in the standard way from application.yml. Also, environments from micronaut.openapi.environments property will set as additional environments, if you want to set specific environment name for openAPI generator.

Default: true

micronaut.openapi.versioning.enabled

Is this property true, micronaut-openapi will process micronaut-router versioning properties and annotations.

Default: true

micronaut.server.context-path

micronaut-http server context path property.

micronaut.internal.openapi.filenames

Final calculated openapi filenames.

jackson.json-view.enabled

micronaut-http property (json-view.enabled).

Default: false

micronaut.openapi.json.view.default.inclusion

Property that determines whether properties that have no view annotations are included in JSON serialization views. If enabled, non-annotated properties will be included; when disabled, they will be excluded.

Default: true

micronaut.openapi.expand.*

Prefix for expandable properties. These properties can be used only for placeholders.

micronaut.openapi.config.file.locations

Config file locations. By default, micronaut-openapi search config in standard path: <project_path>/src/main/resources/
 
You can set your custom paths separated by ,. To set absolute paths use prefix file:, classpath paths use prefix classpath: or use prefix project: to set paths from project directory.

micronaut.openapi.schema.extra.enable

If this property true, you can add some extra schemas to final openapi spec file.

Default: false

micronaut.openapi.schema.duplicate-resolution

System property to set schema duplicate resolution. Available values:
auto - micronaut-openapi automatically add index suffix to duplicate schema.
error - micronaut-openapi throws an exception when found duplicate schema.

Default: auto

micronaut.openapi.schema.name.separator.empty

If this property true, generic separators and inner class separators in schema name will be empty string.

Default: false

micronaut.openapi.schema.name.separator.generic

System property that set generic class separator for schema name generation.

Default: _

micronaut.openapi.schema.name.separator.inner-class

System property that set inner class separator for schema name generation.

Default: .

micronaut.openapi.schema.mapping.*

Properties prefix to set custom schema implementations for selected classes.
For example, if you want to set simple java.lang.String class to some complex org.somepackage.MyComplexType class you need to write:
 
micronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String
 
Also, you can set it in your application.yml file like this:
 
micronaut:
  openapi:
    schema:
      mapping:
        org.somepackage.MyComplexType: java.lang.String
        org.somepackage.MyComplexType2: java.lang.Integer

micronaut.openapi.schema.decorator.prefix
micronaut.openapi.schema.decorator.postfix.*

Properties prefix to set schema name prefix or postfix by package.
For example, if you have some classes with same names in different packages you can set postfix like this:
 
micronaut.openapi.schema.decorator.postfix.org.api.v1_0_0=1_0_0
micronaut.openapi.schema.decorator.postfix.org.api.v2_0_0=2_0_0
 
Also, you can set it in your application.yml file like this:
 
micronaut:
  openapi:
    schema:
      decorator:         postfix:
          org.api.v1_0_0: 1_0_0
          org.api.v2_0_0: 2_0_0
      decorator:         prefix:
          org.api.v1_0_0: public
          org.api.v2_0_0: private
 

*micronaut.openapi.schema.
DEPRECATED: Use *micronaut.openapi.schema.mapping. instead

Properties prefix to set custom schema implementations for selected classes.
For example, if you want to set simple java.lang.String class to some complex org.somepackage.MyComplexType class you need to write:
 
micronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String
 
Also, you can set it in your application.yml file like this:
 
micronaut:
  openapi:
    schema:
      org.somepackage.MyComplexType: java.lang.String
      org.somepackage.MyComplexType2: java.lang.Integer

micronaut.openapi.schema-prefix.
*micronaut.openapi.schema-postfix.

DEPRECATED: Use *micronaut.openapi.schema.decorator.prefix.
and micronaut.openapi.schema.decorator.postfix. instead

Properties prefix to set schema name prefix or postfix by package.
For example, if you have some classes with same names in different packages you can set postfix like this:
 
micronaut.openapi.schema-postfix.org.api.v1_0_0=1_0_0
micronaut.openapi.schema-postfix.org.api.v2_0_0=2_0_0
 
Also, you can set it in your application.yml file like this:
 
micronaut:
  openapi:
    schema-postfix:
      org.api.v1_0_0: 1_0_0
      org.api.v2_0_0: 2_0_0
    schema-prefix:
      org.api.v1_0_0: public
      org.api.v2_0_0: private
 

micronaut.openapi.groups.*

Properties prefix to set custom schema implementations for selected classes.
For example, if you want to set simple 'java.lang.String' class to some complex 'org.somepackage.MyComplexType' class you need to write:
 
-Dmicronaut.openapi.group.my-group1.title="Title 1"
 
Also, you can set it in your application.yml file like this:
 
micronaut:
  openapi:
    group:
      my-group1:
        title: Title 1
        filename: swagger-${group}-${apiVersion}-$6.12.3.yml
      my-group2:
        title: Title 2

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 to the application configuration. For example:

Exposing OpenAPI YAML
micronaut.router.static-resources.swagger.paths=classpath:META-INF/swagger
micronaut.router.static-resources.swagger.mapping=/swagger/**
micronaut:
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
[micronaut]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swagger/**"
micronaut {
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swagger/**"
      }
    }
  }
}
{
  micronaut {
    router {
      static-resources {
        swagger {
          paths = "classpath:META-INF/swagger"
          mapping = "/swagger/**"
        }
      }
    }
  }
}
{
  "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!")
    }
}
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 Custom serializers

You can set custom classes to create different open api schemas for selected classes:

micronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String
micronaut.openapi.schema.org.somepackage.MyComplexType2=java.lang.Integer
micronaut:
  openapi:
    schema:
      org.somepackage.MyComplexType: java.lang.String
      org.somepackage.MyComplexType2: java.lang.Integer
[micronaut]
  [micronaut.openapi]
    [micronaut.openapi.schema]
      "org.somepackage.MyComplexType"="java.lang.String"
      "org.somepackage.MyComplexType2"="java.lang.Integer"
micronaut {
  openapi {
    schema {
      org.somepackage.MyComplexType = "java.lang.String"
      org.somepackage.MyComplexType2 = "java.lang.Integer"
    }
  }
}
{
  micronaut {
    openapi {
      schema {
        "org.somepackage.MyComplexType" = "java.lang.String"
        "org.somepackage.MyComplexType2" = "java.lang.Integer"
      }
    }
  }
}
{
  "micronaut": {
    "openapi": {
      "schema": {
        "org.somepackage.MyComplexType": "java.lang.String",
        "org.somepackage.MyComplexType2": "java.lang.Integer"
      }
    }
  }
}

or by system properties:

-Dmicronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String -Dmicronaut.openapi.schema.org.somepackage.MyComplexType2=java.lang.Integer

or by openapi.properties

micronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String
micronaut.openapi.schema.org.somepackage.MyComplexType2=java.lang.Integer

Also, it can be used for replace classes schema with generics, for example, if you use jaxb generated classes and have custom serializer for JAXBElement class. And you can set custom schemas for different type args. For example if you have this classes structure:

package test.mypackage;

class MyDto {

    public JAXBElement<? extends XmlElement> xmlElement;
    public JAXBElement<? extends XmlElement2> xmlElement2;
    public JAXBElement<? extends XmlElement3> xmlElement3;
}

class XmlElement {
    public String propStr;
}

class XmlElement2 {
    public String propStr2;
}

class XmlElement3 {
    public String propStr3;
}

You can customize classes structure for openapi schema:

package io.micronaut.openapi;

// if you want to use generic from fields with type JAXBElement<T>
class MyJaxbElement<T> {
    public String type;
    public T value;
}

class MyJaxbElement2 {
    public String type;
    public List<String> values;
}

class MyJaxbElement3 {
    public String type;
    public String value;
}

And set openapi properties to map classes to custom openapi schema classes:

micronaut.openapi.schema.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement
micronaut.openapi.schema.io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement2>=io.micronaut.openapi.MyJaxbElement2
micronaut.openapi.schema.io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement3>=io.micronaut.openapi.MyJaxbElement3
micronaut:
  openapi:
    schema:
      io.micronaut.openapi.JAXBElement: io.micronaut.openapi.MyJaxbElement
      io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement2>: io.micronaut.openapi.MyJaxbElement2
      io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement3>: io.micronaut.openapi.MyJaxbElement3
[micronaut]
  [micronaut.openapi]
    [micronaut.openapi.schema]
      "io.micronaut.openapi.JAXBElement"="io.micronaut.openapi.MyJaxbElement"
      "io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement2>"="io.micronaut.openapi.MyJaxbElement2"
      "io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement3>"="io.micronaut.openapi.MyJaxbElement3"
micronaut {
  openapi {
    schema {
      io.micronaut.openapi.JAXBElement = "io.micronaut.openapi.MyJaxbElement"
      io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement2> = "io.micronaut.openapi.MyJaxbElement2"
      io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement3> = "io.micronaut.openapi.MyJaxbElement3"
    }
  }
}
{
  micronaut {
    openapi {
      schema {
        "io.micronaut.openapi.JAXBElement" = "io.micronaut.openapi.MyJaxbElement"
        "io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement2>" = "io.micronaut.openapi.MyJaxbElement2"
        "io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement3>" = "io.micronaut.openapi.MyJaxbElement3"
      }
    }
  }
}
{
  "micronaut": {
    "openapi": {
      "schema": {
        "io.micronaut.openapi.JAXBElement": "io.micronaut.openapi.MyJaxbElement",
        "io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement2>": "io.micronaut.openapi.MyJaxbElement2",
        "io.micronaut.openapi.JAXBElement<test.mypackage.XmlElement3>": "io.micronaut.openapi.MyJaxbElement3"
      }
    }
  }
}
Important! After changing these settings, a complete recompilation of the project is necessary to ensure that the new settings are applied correctly.

11 Schema decorators

If you have some classes with same names in different packages you can set postfix like this:

micronaut.openapi.schema-postfix.org.api.v1_0_0=100
micronaut.openapi.schema-postfix.org.api.v2_0_0=200
micronaut:
  openapi:
    schema-postfix:
      org.api.v1_0_0: 1_0_0
      org.api.v2_0_0: 2_0_0
[micronaut]
  [micronaut.openapi]
    [micronaut.openapi.schema-postfix]
      "org.api.v1_0_0"=100
      "org.api.v2_0_0"=200
micronaut {
  openapi {
    schemaPostfix {
      org.api.v1_0_0 = 100
      org.api.v2_0_0 = 200
    }
  }
}
{
  micronaut {
    openapi {
      schema-postfix {
        "org.api.v1_0_0" = 100
        "org.api.v2_0_0" = 200
      }
    }
  }
}
{
  "micronaut": {
    "openapi": {
      "schema-postfix": {
        "org.api.v1_0_0": 100,
        "org.api.v2_0_0": 200
      }
    }
  }
}

or by system properties:

-Dmicronaut.openapi.schema-postfix.org.api.v1_0_0=1_0_0 -Dmicronaut.openapi.schema-postfix.org.api.v2_0_0=2_0_0

or by openapi.properties

micronaut.openapi.schema-postfix.org.api.v1_0_0=1_0_0
micronaut.openapi.schema-postfix.org.api.v2_0_0=2_0_0
Important! After changing these settings, a complete recompilation of the project is necessary to ensure that the new settings are applied correctly.

12 Kotlin specific

To support incremental annotation processing, you need to explicitly set the path to the project directory through the annotation processor setting micronaut.openapi.project.dir like this:

Gradle
kapt {
    arguments {
        arg("micronaut.openapi.project.dir", projectDir.toString())
    }
}

13 Swagger Annotations

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

Add the Micronaut OpenAPI annotations to compile classpath

compileOnly("io.micronaut.openapi:micronaut-openapi-annotations")
<dependency>
    <groupId>io.micronaut.openapi</groupId>
    <artifactId>micronaut-openapi-annotations</artifactId>
    <scope>provided</scope>
</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 jakarta.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 Mono.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.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")
    @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:
          description: OK
          content:
            text/plain:
              schema:
                type: string
        400:
          description: Invalid Name Supplied
        404:
          description: Person not found

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

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

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

    public void setResult(T r) {
        this.r = 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.

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

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

14 Exposing Endpoints

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

14.1 Enable Endpoints

To process user defined endpoints simply add:

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

14.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 Endpoints

14.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")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-management</artifactId>
    </path>
</annotationProcessorPaths>

implementation("io.micronaut:micronaut-management")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-management</artifactId>
</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"]}]

14.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" \
        } \
      } \
    } \
  ]

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

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

15 Micronaut OpenAPI annotations

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

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

compileOnly("io.micronaut.openapi:micronaut-openapi-annotations")
<dependency>
    <groupId>io.micronaut.openapi</groupId>
    <artifactId>micronaut-openapi-annotations</artifactId>
    <scope>provided</scope>
</dependency>

15.1 @OpenAPIDecorator

The annotation can be used to add suffix and prefix for operationIds. This solves the problem when you have several different controllers, but with same operation names.

For example, when you have 2 controllers with same operations, but use generics:

@OpenAPIDecorator(opIdPrefix = "cats-", opIdSuffix = "-suffix")
@Controller("/cats")
interface MyCatsOperations extends Api<MyRequest, MyResponse> {
}

@OpenAPIDecorator("dogs-")
@Controller("/dogs")
interface MyDogsOperations extends Api<MyRequest, MyResponse> {
}

15.2 @OpenAPIExtraSchema

You can add extra schemas (unused in endpoints) to final specification file. To add unused schemas to openapi file you can use @OpenAPIExtraSchema annotaion.

To add current class to openapi file just add @OpenAPIExtraSchema annotation without any parameters:

@OpenAPIExtraSchema
class UnusedSchema {

    public String field1;
}

Another way, you can set classnames or packages to include / exclude extra schemas from final openapi file:

@OpenAPIExtraSchema(
    // classes to add
    classes = UnusedModel1.class,
    // excluded classes, which marked with `@OpenAPIExtraSchema` annotation
    excludeClasses = ExcludedModel.class,
    // exclude classes by packages
    excludePackages = "io.micronaut.openapi.exclude",
    // include classes by packages
    packages = "io.micronaut.openapi.extra"
)
class Application {
}

15.3 @OpenAPIGroup

You can divide your API description into several separate files using the OpenAPIGroup annotation. With this annotation, you can specify one or more groups that this endpoint will be included in, as well as specify groups from which this endpoint should be excluded. You can annotate method, class or package (subpackages not included!).

For example, you have a controller like this:

@Controller
public class ApiController {

    @OpenAPIGroup(exclude = "v2")
    @Get("/read/{id}")
    public String read(String id) {
        return "OK!";
    }

    @OpenAPIGroup("v2")
    @Post("/save/{id}")
    public String save2(String id, Object body) {
        return "OK!";
    }

    @OpenAPIGroup({"v1", "v2"})
    @Post("/save")
    public String save(Object body) {
        return "OK!";
    }
}

After processing, you will have 2 different swagger files. The first file for group v1 contain the description of the endpoints /save and /read/{id}. The second one will contain the description of the endpoints /save and /save/{id}.

You can read more about groups here: Versions and groups

15.4 @OpenAPIGroupInfo

If you need to describe a particular group with a separate @OpenAPIDefinition annotation, then add the OpenAPIGroupInfo annotation, in which specify the names of the group to which this OpenAPI description belongs. This way you can generate as many different swagger files as you want from one project.

An annotation can be added at the class level or at the package level.

@OpenAPIGroupInfo(
        names = "v1",
        info = @OpenAPIDefinition(
            info = @Info(
                    title = "Public api v1",
                    version = "v1",
                    description = "This is API version 1",
                    license = @License(name = "Apache 2.0", url = "https://foo.bar"),
                    contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
            )
        )
)
@OpenAPIGroupInfo(
        names = "v2",
        info = @OpenAPIDefinition(
            info = @Info(
                    title = "Public api v2",
                    version = "v2",
                    description = "This is API version 2",
                    license = @License(name = "Apache 2.0", url = "https://foo.bar"),
                    contact = @Contact(url = "https://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
            )
        )
)
@OpenAPIDefinition(
        info = @Info(
                title = "Private api",
                version = "${service.version}",
                description = "This is API version 2",
                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);
    }
}

15.5 @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 = "${service.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);
    }
}

15.6 @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.class
})

15.7 @OpenAPISecurity

OpenAPISecurity adds security endpoints.

It is mapped to:

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

15.8 @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;

    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.

16 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
      responses:
        200:
          description: logout 200 response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Object'
        302:
          description: Found
          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
              schema:
                type: string
  /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
      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
      responses:
        302:
          description: Found
          links:
            logout:
              operationId: logout
components:
  schemas:
    AuthorizationResponse:
      required:
      - code
      - state
      type: object
      properties:
        code:
          type: string
          description: an authorization code which the OAuth 2.0 client can exchange
            for an access token
        nonce:
          type: string
          nullable: true
        state:
          type: string
    Object:
      type: object

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
      responses:
        "200":
          description: OpenAPI YAML file describing the API
          content:
            text/plain: {}

To merge both files with the generated OpenAPI definition point, Micronaut searches for additional OpenAPI yaml files in the openapi folder, which is specified by micronaut.openapi.additional.files.

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

Micronaut includes the endpoints defined in those files in the generated output.

17 Generating OpenAPI Views

Micronaut can generate views for your generated OpenApi specification. Currently, Swagger-ui, Redoc, OpenAPI Explorer 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.

17.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
micronaut.router.static-resources.swagger.mapping=/swaggerYAML/**
micronaut:
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swaggerYAML/**
[micronaut]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swaggerYAML/**"
micronaut {
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swaggerYAML/**"
      }
    }
  }
}
{
  micronaut {
    router {
      static-resources {
        swagger {
          paths = "classpath:META-INF/swagger"
          mapping = "/swaggerYAML/**"
        }
      }
    }
  }
}
{
  "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…​.

17.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, OpenAPI Explorer
micronaut.openapi.views.spec = swagger-ui.enabled=true,\
    redoc.enabled=true, \
    openapi-explorer.enabled=true, \
    rapidoc.enabled=true, \
    rapidoc.bg-color=#14191f, \
    rapidoc.text-color=#aec2e0, \
    rapidoc.sort-endpoints-by=method

17.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
-Dmicronaut.openapi.views.spec=redoc.enabled=true,rapidoc.enabled=true,openapi-explorer.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,openapi-explorer.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,openapi-explorer.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,openapi-explorer.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,openapi-explorer.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,openapi-explorer.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,openapi-explorer.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.1.0</version>
        <executions>
          <execution>
            <goals>
              <goal>set-system-properties</goal>
            </goals>
            <configuration>
              <properties>
                <property>
                  <name>micronaut.openapi.views.spec</name>
                  <value>rapidoc.enabled=true,openapi-explorer.enabled=true,swagger-ui.enabled=true,swagger-ui.theme=flattop</value>
                </property>
              </properties>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
</build>

17.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.js.url

Override path to use to find the js file. Path must contain a files named swagger-ui.css, swagger-ui-bundle.js, swagger-ui-standalone-present.js. The value can be a URL path like: "https://unpkg.com/swagger-ui-dist/". Must contain trailing slash.,

swagger-ui.spec.url

Override path in swagger file path line. If you don’t set, then value will be <context-path>/swagger/<spec_filename>. You can use the placeholder ${filename} it will be replaced with the final name of the generated swagger file.

swagger-ui.copy-resources

Copy or not local JS and CSS resources. Set to false if you are using external resources (e.g. https://unpkg.com/swagger-ui-dist/). Default true

swagger-ui.theme

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

swagger-ui.theme.url

Override path to use to find the theme CSS file.,

swagger-ui.copy-theme

Copy or not local theme CSS resources. Set to false if you are using external resources (e.g. https://swagger-theme.com/flattop.css). Default true

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

swagger-ui.persistAuthorization

See Swagger UI Configuration for a description.

To expose 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
micronaut.router.static-resources.swagger.mapping=/swagger/**
micronaut.router.static-resources.swagger-ui.paths=classpath:META-INF/swagger/views/swagger-ui
micronaut.router.static-resources.swagger-ui.mapping=/swagger-ui/**
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/**
[micronaut]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swagger/**"
      [micronaut.router.static-resources.swagger-ui]
        paths="classpath:META-INF/swagger/views/swagger-ui"
        mapping="/swagger-ui/**"
micronaut {
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swagger/**"
      }
      swaggerUi {
        paths = "classpath:META-INF/swagger/views/swagger-ui"
        mapping = "/swagger-ui/**"
      }
    }
  }
}
{
  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/**"
        }
      }
    }
  }
}
{
  "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.

17.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 e.g.:

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:

micronaut.security.enabled=true
micronaut.security.token.jwt.enabled=true
micronaut.security.token.jwt.signatures.jwks.okta.url=https://mycompany.okta.com/oauth2/default/v1/keys
micronaut.security.intercept-url-map[0].pattern=/swagger-ui/**
micronaut.security.intercept-url-map[0].httpMethod=GET
micronaut.security.intercept-url-map[0].access[0]=isAnonymous()
micronaut.security.intercept-url-map[1].pattern=/swagger/**
micronaut.security.intercept-url-map[1].access[0]=isAnonymous()
micronaut.router.static-resources.swagger.paths=classpath:META-INF/swagger
micronaut.router.static-resources.swagger.mapping=/swagger/**
micronaut.router.static-resources.swagger-ui.paths=classpath:META-INF/swagger/views/swagger-ui
micronaut.router.static-resources.swagger-ui.mapping=/swagger-ui/**
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/**
[micronaut]
  [micronaut.security]
    enabled=true
    [micronaut.security.token]
      [micronaut.security.token.jwt]
        enabled=true
        [micronaut.security.token.jwt.signatures]
          [micronaut.security.token.jwt.signatures.jwks]
            [micronaut.security.token.jwt.signatures.jwks.okta]
              url="https://mycompany.okta.com/oauth2/default/v1/keys"
    [[micronaut.security.intercept-url-map]]
      pattern="/swagger-ui/**"
      httpMethod="GET"
      access=[
        "isAnonymous()"
      ]
    [[micronaut.security.intercept-url-map]]
      pattern="/swagger/**"
      access=[
        "isAnonymous()"
      ]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swagger/**"
      [micronaut.router.static-resources.swagger-ui]
        paths="classpath:META-INF/swagger/views/swagger-ui"
        mapping="/swagger-ui/**"
micronaut {
  security {
    enabled = true
    token {
      jwt {
        enabled = true
        signatures {
          jwks {
            okta {
              url = "https://mycompany.okta.com/oauth2/default/v1/keys"
            }
          }
        }
      }
    }
    interceptUrlMap = [{
        pattern = "/swagger-ui/**"
        httpMethod = "GET"
        access = ["isAnonymous()"]
      }, {
        pattern = "/swagger/**"
        access = ["isAnonymous()"]
      }]
  }
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swagger/**"
      }
      swaggerUi {
        paths = "classpath:META-INF/swagger/views/swagger-ui"
        mapping = "/swagger-ui/**"
      }
    }
  }
}
{
  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/**"
        }
      }
    }
  }
}
{
  "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.

17.5 Redoc

Views support 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.js.url

Override path to use to find the js file. Path must contain a file named redoc.standalone.js. The value can be a URL path like: "https://unpkg.com/redoc/bundles/". Must contain trailing slash.

redoc.spec.url

Override path in swagger file path line. If you don’t set, then value will be <context-path>/swagger/<spec_filename>. You can use the placeholder ${filename} it will be replaced with the final name of the generated swagger file.

redoc.copy-resources

Copy or not local JS and CSS resources. Set to false if you are using external resources (e.g. https://unpkg.com/redoc/bundles/). Default true

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 config for a description of the above properties.

To expose 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
micronaut.router.static-resources.swagger.mapping=/swagger/**
micronaut.router.static-resources.redoc.paths=classpath:META-INF/swagger/views/redoc
micronaut.router.static-resources.redoc.mapping=/redoc/**
micronaut:
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      redoc:
        paths: classpath:META-INF/swagger/views/redoc
        mapping: /redoc/**
[micronaut]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swagger/**"
      [micronaut.router.static-resources.redoc]
        paths="classpath:META-INF/swagger/views/redoc"
        mapping="/redoc/**"
micronaut {
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swagger/**"
      }
      redoc {
        paths = "classpath:META-INF/swagger/views/redoc"
        mapping = "/redoc/**"
      }
    }
  }
}
{
  micronaut {
    router {
      static-resources {
        swagger {
          paths = "classpath:META-INF/swagger"
          mapping = "/swagger/**"
        }
        redoc {
          paths = "classpath:META-INF/swagger/views/redoc"
          mapping = "/redoc/**"
        }
      }
    }
  }
}
{
  "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.

17.6 OpenAPI Explorer

Views support OpenAPI Explorer, to enable it use openapi-explorer.enabled=true.

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

openapi-explorer.enabled

true or false When 'true' the OpenAPI Explorer view is generated.

openapi-explorer.js.url

Override path to use to find the js and other resource files. Path must contain a file named openapi-explorer.min.js. The value can be a URL path like: "https://unpkg.com/openapi-explorer/dist/browser/". Must contain trailing slash.

openapi-explorer.spec.url

Override path in swagger file path line. If you don’t set, then value will be <context-path>/swagger/<spec_filename>. You can use the placeholder ${filename} it will be replaced with the final name of the generated swagger file.

openapi-explorer.copy-resources

Copy or not local JS and CSS resources. Set to false if you are using external resources (e.g. https://unpkg.com/openapi-explorer/dist/browser/openapi-explorer.min.js). Default true

openapi-explorer.expand-single-schema-field

openapi-explorer.expand-default-server-variables

openapi-explorer.menu-toggle

openapi-explorer.only-required-in-samples

openapi-explorer.payload-sample-idx

openapi-explorer.sort-props-alphabetically

openapi-explorer.untrusted-spec

openapi-explorer.expand-responses

openapi-explorer.show-extensions

openapi-explorer.native-scrollbars

openapi-explorer.path-in-middle-panel

openapi-explorer.suppress-warnings

openapi-explorer.hide-hostname

openapi-explorer.disable-search

openapi-explorer.json-sample-expand-level

openapi-explorer.scroll-y-offset

openapi-explorer.hide-download-button

openapi-explorer.no-auto-auth

openapi-explorer.theme

openapi-explorer.hide-single-request-sample-tab

openapi-explorer.required-props-first

openapi-explorer.hide-loading

See OpenAPI Explorer Options for a description of the above properties.

To expose the openapi-explorer views, you also must expose the generated yaml:

Exposing Swagger YAML And OpenAPI Explorer Views
micronaut.router.static-resources.swagger.paths=classpath:META-INF/swagger
micronaut.router.static-resources.swagger.mapping=/swagger/**
micronaut.router.static-resources.openapi-explorer.paths=classpath:META-INF/swagger/views/openapi-explorer
micronaut.router.static-resources.openapi-explorer.mapping=/openapi-explorer/**
micronaut:
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      openapi-explorer:
        paths: classpath:META-INF/swagger/views/openapi-explorer
        mapping: /openapi-explorer/**
[micronaut]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swagger/**"
      [micronaut.router.static-resources.openapi-explorer]
        paths="classpath:META-INF/swagger/views/openapi-explorer"
        mapping="/openapi-explorer/**"
micronaut {
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swagger/**"
      }
      openapiExplorer {
        paths = "classpath:META-INF/swagger/views/openapi-explorer"
        mapping = "/openapi-explorer/**"
      }
    }
  }
}
{
  micronaut {
    router {
      static-resources {
        swagger {
          paths = "classpath:META-INF/swagger"
          mapping = "/swagger/**"
        }
        openapi-explorer {
          paths = "classpath:META-INF/swagger/views/openapi-explorer"
          mapping = "/openapi-explorer/**"
        }
      }
    }
  }
}
{
  "micronaut": {
    "router": {
      "static-resources": {
        "swagger": {
          "paths": "classpath:META-INF/swagger",
          "mapping": "/swagger/**"
        },
        "openapi-explorer": {
          "paths": "classpath:META-INF/swagger/views/openapi-explorer",
          "mapping": "/openapi-explorer/**"
        }
      }
    }
  }
}

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

17.7 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.js.url

Override path to use to find the js file. Path must contain a file named rapidoc-min.js. The value can be a URL path like: "https://unpkg.com/rapidoc/dist/". Must contain trailing slash.

rapidoc.spec.url

Override path in swagger file path line. If you don’t set, then value will be <context-path>/swagger/<spec_filename>. You can use the placeholder ${filename} it will be replaced with the final name of the generated swagger file.

rapidoc.copy-resources

Copy or not local JS and CSS resources. Set to false if you are using external resources (e.g. https://unpkg.com/rapidoc/dist/). Default true

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 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
micronaut.router.static-resources.swagger.mapping=/swagger/**
micronaut.router.static-resources.rapidoc.paths=classpath:META-INF/swagger/views/rapidoc
micronaut.router.static-resources.rapidoc.mapping=/rapidoc/**
micronaut:
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      rapidoc:
        paths: classpath:META-INF/swagger/views/rapidoc
        mapping: /rapidoc/**
[micronaut]
  [micronaut.router]
    [micronaut.router.static-resources]
      [micronaut.router.static-resources.swagger]
        paths="classpath:META-INF/swagger"
        mapping="/swagger/**"
      [micronaut.router.static-resources.rapidoc]
        paths="classpath:META-INF/swagger/views/rapidoc"
        mapping="/rapidoc/**"
micronaut {
  router {
    staticResources {
      swagger {
        paths = "classpath:META-INF/swagger"
        mapping = "/swagger/**"
      }
      rapidoc {
        paths = "classpath:META-INF/swagger/views/rapidoc"
        mapping = "/rapidoc/**"
      }
    }
  }
}
{
  micronaut {
    router {
      static-resources {
        swagger {
          paths = "classpath:META-INF/swagger"
          mapping = "/swagger/**"
        }
        rapidoc {
          paths = "classpath:META-INF/swagger/views/rapidoc"
          mapping = "/rapidoc/**"
        }
      }
    }
  }
}
{
  "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.

17.8 RapiPdf

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

RapiPdf supports the following options:

rapipdf.enabled

true or false,

rapipdf.js.url

Override path to use to find the js file. Path must contain a file named rapdipdf-min.js. The value can be a URL path like: "https://unpkg.com/rapipdf/dist/". Must contain trailing slash.,

rapipdf.spec.url

Override path in swagger file path line. If you don’t set, then value will be <context-path>/swagger/<spec_filename>. You can use the placeholder ${filename} it will be replaced with the final name of the generated swagger file.

rapipdf.copy-resources

Copy or not local JS and CSS resources. Set to false if you are using external resources (e.g. https://unpkg.com/rapipdf/dist/). Default true

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.

18 Conversion to Asciidoc

You also can generate file in Asciidoc format. Then you can convert Asciidoc to another format: Markdown, HTML, PDF or DOCX.

To enable generation file in adoc format just set property -Dmicronaut.openapi.adoc.enabled=true and add micronaut-openapi-adoc dependency to classpath:

annotationProcessor("io.micronaut.openapi:micronaut-openapi-adoc")

Adoc generator based on Apache Freemarker templates, and you can override full template or just customize part of them.

Available options
micronaut.openapi.adoc.enabled System property that enables or disables open api asciidoc conversion. Default: true

micronaut.openapi.adoc.template.dir

Template directory path.

Default: classpath:/template

micronaut.openapi.adoc.template.filename

Main template filename.

Default: openApiDoc.ftl (in classpath)

micronaut.openapi.adoc.output.dir

Output directory path.

Default: standard micronaut openapi output directory path

micronaut.openapi.adoc.output.filename

Final adoc file name.

Default: the same as openapi spec. filename

micronaut.openapi.adoc.templates.*

Prefix for custom template names. You can override any of the templates that are used in the default implementation to change only part of the resulting document.

19 Versions and groups

Micronaut OpenAPI allows you to split the description of endpoints into several files. To do this, you can use the versioning mechanism built into Micronaut (using the @Version annotation) and / or group endpoints using the @OpenAPIGroup annotation or configuration.

19.1 Micronaut versioning

How to use the @Version annotation can be read here: Micronaut API versioning.

Micronaut OpenAPI support this versioning and automatically add parameters or headers with version from your application.yml.

For example:

VersionedController.java
@Controller("/versioned")
public class VersionedController {

    @Version("1")
    @Get("/hello")
    public String helloV1() {
        return "helloV1";
    }

    @Version("2")
    @Post("/hello")
    public String helloV2(UserDto userDto) {
        return "helloV2";
    }

    @Post("/common")
    public String common() {
        return null;
    }

    public static class UserDto {

        public String name;
        public int age;
        public String secondName;
        @NotNull
        public String address;
    }
}
application.yml
micronaut.router.versioning.enabled=true
micronaut.router.versioning.parameter.enabled=true
micronaut.router.versioning.parameter.names=version
micronaut:
  router:
    versioning:
      enabled: true
      parameter:
        enabled: true
        names: version
[micronaut]
  [micronaut.router]
    [micronaut.router.versioning]
      enabled=true
      [micronaut.router.versioning.parameter]
        enabled=true
        names="version"
micronaut {
  router {
    versioning {
      enabled = true
      parameter {
        enabled = true
        names = "version"
      }
    }
  }
}
{
  micronaut {
    router {
      versioning {
        enabled = true
        parameter {
          enabled = true
          names = "version"
        }
      }
    }
  }
}
{
  "micronaut": {
    "router": {
      "versioning": {
        "enabled": true,
        "parameter": {
          "enabled": true,
          "names": "version"
        }
      }
    }
  }
}

After processing the annotation processor, you will see 2 swagger files:

Generated swagger-1.yml
openapi: 3.0.1
info:
  title: Api
  version: "1.0"
paths:
  /versioned/common:
    post:
      operationId: common
      parameters:
      - name: version
        in: query
        description: API version
        schema:
          type: string
      responses:
        "200":
          description: common 200 response
          content:
            application/json:
              schema:
                type: string
  /versioned/hello:
    get:
      operationId: helloV1
      parameters:
      - name: version
        in: query
        description: API version
        schema:
          type: string
      responses:
        "200":
          description: helloV1 200 response
          content:
            application/json:
              schema:
                type: string
Generated swagger-2.yml
openapi: 3.0.1
info:
  title: Api
  version: "2.0"
paths:
  /versioned/common:
    post:
      operationId: common
      parameters:
      - name: version
        in: query
        description: API version
        schema:
          type: string
      responses:
        "200":
          description: common 200 response
          content:
            application/json:
              schema:
                type: string
  /versioned/hello:
    post:
      operationId: helloV2
      parameters:
      - name: version
        in: query
        description: API version
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userDto:
                  $ref: '#/components/schemas/UserDto'
        required: true
      responses:
        "200":
          description: helloV2 200 response
          content:
            application/json:
              schema:
                type: string
components:
  schemas:
    UserDto:
      required:
      - address
      type: object
      properties:
        name:
          type: string
        age:
          type: integer
          format: int32
        secondName:
          type: string
        address:
          type: string

19.2 OpenAPI groups

To group endpoints according to different characteristics (API version, entity type, public / private, etc.), you can use the @OpenAPIGroup annotation.

With this annotation, you can specify which groups this endpoint belongs to and which groups it should be excluded from. An annotation can be written at the method, class or package level. You can also set binding to a group through the configuration, if you do not want to add annotations in the code.

You can set some additional settings for the group through system properties / application.yml / openapi.properties:

Usage example:

@Controller
public class ApiController {

    @OpenAPIGroup("v2")
    @Post("/save/{id}")
    public String save2(String id, Object body) {
        return "OK!";
    }

    @OpenAPIGroup({"v1", "v2"})
    @Post("/save")
    public String save(Object body) {
        return "OK!";
    }
}
micronaut.openapi.groups.v1.display-name=My API v1
micronaut.openapi.groups.v1.primary=true
micronaut.openapi.groups.v1.filename=myapiv1
micronaut.openapi.groups.v1.common-exclude=true
micronaut.openapi.groups.v1.packages=com.micronaut.controller.v1.*
micronaut.openapi.groups.v1.packages-exclude[0]=com.micronaut.controller.v1
micronaut.openapi.groups.v1.packages-exclude[1]=com.micronaut.controller
micronaut.openapi.groups.v2.display-name=My API v2
micronaut.openapi.groups.v3.testv3group.packages-exclude[0]=com.micronaut.controller.v3
micronaut.openapi.groups.v3.testv3group.packages-exclude[1]=com.micronaut.controller
micronaut:
  openapi:
    groups:
      v1:
        #The title that will be displayed in the group selector in the UI
        display-name: My API v1
        # A flag indicating that the swagger UI should select this group in the selector by default.
        primary: true
        # The name of the final swagger file. If it is not set, then by default the name will be generated according
        # to the following pattern: swagger-<version>-<groupName>-<apiVersion>. version - application version from the main
        # OpenAPIDefinition annotation, api-version - version from micronaut @Version annotation
        filename: myapiv1
        # Whether to exclude common endpoints from this group
        common-exclude: true
        # An additional way to add or remove endpoints from a given batch group.
        # Strong package matching and inclusion of all subpackages is supported (* symbol)
        packages: com.micronaut.controller.v1.*
        packages-exclude:
          - com.micronaut.controller.v1
          - com.micronaut.controller
      v2:
        display-name: My API v2
      "v3.testv3group":
        packages-exclude:
         - com.micronaut.controller.v3
         - com.micronaut.controller
[micronaut]
  [micronaut.openapi]
    [micronaut.openapi.groups]
      [micronaut.openapi.groups.v1]
        display-name="My API v1"
        primary=true
        filename="myapiv1"
        common-exclude=true
        packages="com.micronaut.controller.v1.*"
        packages-exclude=[
          "com.micronaut.controller.v1",
          "com.micronaut.controller"
        ]
      [micronaut.openapi.groups.v2]
        display-name="My API v2"
      [micronaut.openapi.groups."v3.testv3group"]
        packages-exclude=[
          "com.micronaut.controller.v3",
          "com.micronaut.controller"
        ]
micronaut {
  openapi {
    groups {
      v1 {
        displayName = "My API v1"
        primary = true
        filename = "myapiv1"
        commonExclude = true
        packages = "com.micronaut.controller.v1.*"
        packagesExclude = ["com.micronaut.controller.v1", "com.micronaut.controller"]
      }
      v2 {
        displayName = "My API v2"
      }
      v3.testv3group {
        packagesExclude = ["com.micronaut.controller.v3", "com.micronaut.controller"]
      }
    }
  }
}
{
  micronaut {
    openapi {
      groups {
        v1 {
          display-name = "My API v1"
          primary = true
          filename = "myapiv1"
          common-exclude = true
          packages = "com.micronaut.controller.v1.*"
          packages-exclude = ["com.micronaut.controller.v1", "com.micronaut.controller"]
        }
        v2 {
          display-name = "My API v2"
        }
        "v3.testv3group" {
          packages-exclude = ["com.micronaut.controller.v3", "com.micronaut.controller"]
        }
      }
    }
  }
}
{
  "micronaut": {
    "openapi": {
      "groups": {
        "v1": {
          "display-name": "My API v1",
          "primary": true,
          "filename": "myapiv1",
          "common-exclude": true,
          "packages": "com.micronaut.controller.v1.*",
          "packages-exclude": ["com.micronaut.controller.v1", "com.micronaut.controller"]
        },
        "v2": {
          "display-name": "My API v2"
        },
        "v3.testv3group": {
          "packages-exclude": ["com.micronaut.controller.v3", "com.micronaut.controller"]
        }
      }
    }
  }
}

After processing the annotation processor, you will get 3 different swagger files.

Integration with UI

In rapidoc and redoc do not support multiple files at the same time, so for normal API display, you will have to complete the template yourself.

Swagger UI has support for multiple files out of the box. The example above would look something like this:

swagger ui with groups

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

20.1 Compile Time Resolution

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

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

20.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*", "/**/openapi-explorer*"})
public class OpenApiViewCookieContextPathFilter implements HttpServerFilter {

    private final Cookie contextPathCookie;

    OpenApiViewCookieContextPathFilter(@Value("${micronaut.server.context-path}") String contextPath) {
        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*", "/**/openapi-explorer*"}
)
@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);
		}
	}

}

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

21 Guides

See the following list of guides to learn more about working with OpenAPI in the Micronaut Framework:

22 Repository

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

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