OpenAPI/Swagger Support

Configuration to integrate Micronaut and OpenAPI/Swagger

Version: 1.2.0

1 Introduction

Micronaut includes experimental support for producing OpenAPI (Swagger) YAML at compilation time. Micronaut will at compile time produce a Swagger 3.x compliant YAML file just based on the regular Micronaut annotations and the javadoc comments within your code.

You can customize the generated Swagger using the standard Swagger Annotations.

2 Using the Micronaut CLI

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

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

This will create a project with the minimum necessary configuration for OpenAPI.

If you have already created a Micronaut project and will like to add Swagger support, you can simply follow instructions in subsequent sections.

3 Getting Started with Swagger

To enable this support you should add the following dependencies to your build configuration:

Adding the Swagger Dependencies
annotationProcessor "io.micronaut.configuration:micronaut-openapi" (1)
compile "io.swagger.core.v3:swagger-annotations" (2)
1 The openapi configuration gets added into the annotation processor scope
2 The Swagger Annotations are added to the compile classpath
For Kotlin the openapi dependency should be in the kapt scope and for Groovy in the compileOnly scope.

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

import io.micronaut.runtime.Micronaut;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;


@OpenAPIDefinition(
        info = @Info(
                title = "Hello World",
                version = "0.0",
                description = "My API",
                license = @License(name = "Apache 2.0", url = "http://foo.bar"),
                contact = @Contact(url = "http://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}
import io.micronaut.runtime.Micronaut
import groovy.transform.CompileStatic
import io.swagger.v3.oas.annotations.*
import io.swagger.v3.oas.annotations.info.*


@OpenAPIDefinition(
        info = @Info(
                title = "Hello World",
                version = "0.0",
                description = "My API",
                license = @License(name = "Apache 2.0", url = "http://foo.bar"),
                contact = @Contact(url = "http://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
@CompileStatic
class Application {
    static void main(String[] args) {
        Micronaut.run(Application)
    }
}
import io.micronaut.runtime.Micronaut
import io.swagger.v3.oas.annotations.OpenAPIDefinition
import io.swagger.v3.oas.annotations.info.Contact
import io.swagger.v3.oas.annotations.info.Info
import io.swagger.v3.oas.annotations.info.License


@OpenAPIDefinition(
        info = Info(
                title = "Hello World",
                version = "0.0",
                description = "My API",
                license = License(name = "Apache 2.0", url = "http://foo.bar"),
                contact = Contact(url = "http://gigantic-server.com", name = "Fred", email = "Fred@gigagantic-server.com")
        )
)
object Application {

    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.run(Application.javaClass)
    }
}

With that in place you compile your project and a Swagger YAML file will be generated to the META-INF/swagger directory of your project’s class output. For example the above configuration for Java will be generated to build/classes/java/main/META-INF/swagger/hello-world-0.0.yml.

The previously defined annotations will produce YAML like the following:

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

4 Exposing Swagger Output

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

Exposing Swagger YAML
micronaut:
    router:
        static-resources:
            swagger:
                paths: classpath:META-INF/swagger
                mapping: /swagger/**

With the above configuration in place when you run your application you can access your Swagger documentation at http://localhost:8080/swagger/hello-world-0.0.yml.

5 Controllers and Swagger Annotations

By default Micronaut will automatically at compile time build out the Swagger YAML definition from your defined controllers and methods. For example given the following class:

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;
import io.reactivex.Single;


@Validated
@Controller("/")
public class HelloController {

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


@Validated
@Controller("/")
class HelloController {

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


@Validated
@Controller("/")
open class HelloController {

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

The resulting output will be:

Example Generated Swagger Output
openapi: 3.0.1
info:
  title: Hello World
  description: My API
  contact:
    name: Fred
    url: http://gigantic-server.com
    email: Fred@gigagantic-server.com
  license:
    name: Apache 2.0
    url: http://foo.bar
  version: "0.0"
paths:
  /hello/{name}:
    get:
      description: ""
      operationId: index
      parameters:
      - name: name
        in: path
        description: The person's name
        required: true
        schema:
          type: string
      responses:
        default:
          description: The greeting
          content:
            text/plain:
              schema:
                type: string

Notice how the javadoc comments are used to fill out the description of the API. If this is not desirable then you can take full control by augmenting your definition with Swagger annotations:

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;
import io.reactivex.Single;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;

import javax.validation.constraints.NotBlank;


@Controller("/")
@Validated
public class HelloController {

    /**
     * @param name The person's name
     * @return The greeting message
     */
    @Get(uri="/greetings/{name}", produces= MediaType.TEXT_PLAIN)
    @Operation(summary = "Greets a person",
            description = "A friendly greeting is returned"
    )
    @ApiResponse(
            content = @Content(mediaType = "text/plain",
                    schema = @Schema(type="string"))
    )
    @ApiResponse(responseCode = "400", description = "Invalid Name Supplied")
    @ApiResponse(responseCode = "404", description = "Person not found")
    @Tag(name = "greeting")
    public Single<String> greetings(@Parameter(description="The name of the person") @NotBlank String name) {
        return Single.just("Hello " + name + ", How are you doing?");
    }
}
@Controller("/")
@Validated
class HelloController {

    /**
     * @param name The person's name
     * @return The greeting message
     */
    @Get(uri="/greetings/{name}", produces= MediaType.TEXT_PLAIN)
    @Operation(summary = "Greets a person",
            description = "A friendly greeting is returned"
    )
    @ApiResponse(
            content = @Content(mediaType = "text/plain",
                    schema = @Schema(type="string"))
    )
    @ApiResponse(responseCode = "400", description = "Invalid Name Supplied")
    @ApiResponse(responseCode = "404", description = "Person not found")
    @Tag(name = "greeting")
    Single<String> greetings(@Parameter(description="The name of the person") @NotBlank String name) {
        return Single.just("Hello $name, How are you doing?")
    }
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import io.reactivex.Single
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag


@Controller("/")
@Validated
open class HelloController {

    /**
     * @param name The person's name
     * @return The greeting message
     */
    @Get(uri = "/greetings/{name}", produces = [MediaType.TEXT_PLAIN])
    @Operation(summary = "Greets a person", description = "A friendly greeting is returned")
    // Please Note: Repeatable Annotations with non-SOURCE retentions are not yet supported with Kotlin so we are using `@ApiResponses`
    // instead of `@ApiResponse`, see https://youtrack.jetbrains.com/issue/KT-12794
    @ApiResponses(
            ApiResponse(content = [Content(mediaType = "text/plain", schema = Schema(type = "string"))]),
            ApiResponse(responseCode = "400", description = "Invalid Name Supplied"),
            ApiResponse(responseCode = "404", description = "Person not found")
    )
    @Tag(name = "greeting")
    open fun greetings(name: String): Single<String> {
        return Single.just("Hello $name, how are you doing?")
    }
}

The resulting output will be:

Example Generated Swagger Output
openapi: 3.0.1
info:
  title: Hello World
  description: My API
  contact:
    name: Fred
    url: http://gigantic-server.com
    email: Fred@gigagantic-server.com
  license:
    name: Apache 2.0
    url: http://foo.bar
  version: "0.0"
paths:
  /greetings/{name}:
    get:
      tags:
      - greeting
      summary: Greets a person
      description: A friendly greeting is returned
      operationId: greetings
      parameters:
      - name: name
        in: path
        description: The name of the person
        required: true
        schema:
          minLength: 1
          type: string
      responses:
        default:
          content:
            text/plain:
              schema:
                type: string
        400:
          description: Invalid Name Supplied
        404:
          description: Person not found

6 Schemas and POJOs

If you return types are not simple strings and primitive types then Micronaut will attempt to generate a Schema definition. You can customize the generation of the Schema by using the @Schema annotation on your POJO. For example:

Using the @Schema Annotation
@Schema(name="MyPet", description="Pet description") (1)
class Pet {
    private PetType type;
    private int age;
    private String name;

    public void setAge(int a) {
        age = a;
    }

    /**
     * The age
     */
    @Schema(description="Pet age", maximum="20") (2)
    public int getAge() {
        return age;
    }

    public void setName(String n) {
        name = n;
    }

    @Schema(description="Pet name", maxLength=20)
    public String getName() {
        return name;
    }

    public void setType(PetType t) {
        type = t;
    }

    public PetType getType() {
        return type;
    }
}

enum PetType {
    DOG, CAT;
}
1 The @Schema annotation is used to customize the name of the schema
2 Properties can be customized too.

7 Schemas and Meta Annotations

If you don’t have control of the source code and don’t want to have to annotation 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.

8 Schemas and Generics

If a method return type includes generics then these will included when calculating the schema name. For example the following:

Swagger returns types and generics
class Response<T> {
    private T r;
    public T getResult() {
        return r;
    };
}

@Controller("/")
class MyController {

    @Put("/")
    public Response<Pet> updatePet(Pet pet) {
        ...
    }
}

Will result in a schema called #/components/schemas/Response<Pet> being generated. If you wish to alter the name of the schema you can do so with the @Schema annotation:

Changing the name of response schema
@Put("/")
@Schema(name="ResponseOfPet")
public Response<Pet> updatePet(Pet pet) {
    ...
}

In the above case the generated schema will be named #/components/schemas/ResponseOfPet.

9 Merging Schemas

Often times you might want to generate OpenAPI (Swagger) YAML for built-in endpoints or paths from some other modules, such as security. In order to generate YAML including all that information, Micronaut supports merging of multiple OpenAPI YAML files. So, you can create OpenAPI YAML files manually at some predefined path from where the information will then be merged into the final YAML file.

To configure the path for additional swagger files you need to set System property micronaut.openapi.additional.files. For example:

micronaut.openapi.additional.files={project.home}/src/test/resources/swagger