Adding OpenAPI Specifications to Spring Boot RESTful APIs

The OpenAPI Specification (OAS) defines

a standard, programming language-agnostic interface description for REST APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic“.

This post demonstrates how to add OpenAPI specifications (version 3.0.1) to document existing RESTful APIs in a Spring Boot/Spring MVC project. This would be useful when you have an existing application and want to expose its APIs via OpenAPI. I will walk through how to setup the project and update the codes to generate the desired documentation.

Project Setup

I use the springdoc-openapi library to generate the OpenAPI documentation. Setting this up is straight forward, just add the following dependencies in your Maven pom file.

<dependency>
     <groupId>org.springdoc</groupId>
     <artifactId>springdoc-openapi-core</artifactId>
     <version>1.1.44</version>
</dependency>
<dependency>
     <groupId>org.springdoc</groupId>
     <artifactId>springdoc-openapi-ui</artifactId>
     <version>1.2.6</version>
</dependency>

Once the above is included, the OpenAPI documentation will be generated for all RESTful APIs in the application and made available at the path /v3/api-docs. For example

http://localhost:8080/v3/api-docs/

The above URL generates the OpenAPI document in json. For yaml format, use the following URL

http://localhost:8080/v3/api-docs.yaml

The above Maven also includes Swagger UI which can be accessed via the URL

http://localhost:8080/swagger-ui.html

swaggerui

Springdoc OpenAPI Configuration

Now we have added the springdoc library, we can start customize our API codes to generate the OpenAPI documentation.

General API Info & Servers

General information about the APIs and server URLs are configured under the @OpenAPIDefinition annotation. This is used to provide general metadata information about the APIs such as version, contact and licencing.

@OpenAPIDefinition(
     info = @Info(
               title = "Order API",
               version = "v2",
	       description = "This app provides REST APIs for order entities",
	       contact = @Contact(
				name = "Raymond",
				email = "admin@orderapi.com",
				url = "http://orderapi.com/support"
			)
		),
            ),
     servers = {
            @Server( 
               url="<dev url>",
               description="DEV Server"
            ),
            @Server( 
               url="<prod url>",
               description="PROD Server"
            )
     }
)
public class OpenApiConfig {
}

The corresponding OpenAPI document for the above annotation is shown below

openapi: 3.0.1
info:
  title: Order API
  description: This app provides REST APIs for order entities
  contact:
    name: Raymond
    url: http://orderapi.com/support
    email: admin@orderapi.com
  version: v2
servers:
- url: <dev url>
  description: DEV Server
  variables: {}
- url: <prod url>
  description: PROD Server
  variables: {}

Security

There are two steps for defining API security in OpenAPI.

First, define a global security schema. For example, an OAuth2 schema using JWT can be defined using the @SecurityScheme annotation below:

@SecurityScheme(
     name = "myOauth2Security",
     type = SecuritySchemeType.OAUTH2,
     in = SecuritySchemeIn.HEADER,
     bearerFormat = "jwt",
     flows = @OAuthFlows(
               implicit = @OAuthFlow(
                  authorizationUrl = "http://url.com/auth",
                  scopes = {
                     @OAuthScope(name = "read", description = "read access"),
                     @OAuthScope(name = "write", description = "Write access")
                  }
             )
     )
)

Now that a global security schema has been defined, we can apply it to any API by annotating the method in the REST controller with @Operation annotation and specify the security with @SecurityRequirement annotation by referring to the schema above by its name.

@RestController
public class OrderController {
... 
     @Operation(
          security = @SecurityRequirement(name = "myOauth2Security", scopes = "write"),
          // ... other attributes 
     )
     @PostMapping(value = "/order", consumes = MediaType.APPLICATION_JSON_VALUE)
     public void postOrder(OrderDto order) {
          // Implementation codes
     }
...

Paths & Operations

Springdoc is smart enough to interpret the path of an endpoint from its Spring MVC request mapping annotation, i.e. @PostMapping, @GetMapping etc.

To provide more information about an endpoint, use @Operation annotation as shown in the previous section. Below is a complete example for an endpoint to post a new order to the backend server:

@RestController
public class OrderController {
... 
     @Operation(
          summary = "Create a new order",
          description = "Use this endpoint to create a new order in the backend",
          security = @SecurityRequirement(name = "myOauth2Security", scopes = "write"),
          responses= {
              @ApiResponse(responseCode = "201", description = "Success"),
              @ApiResponse(responseCode = "403", description = "Forbidden"),
              @ApiResponse(responseCode = "500", description = "Server Error")
          }
     )
     @ResponseStatus(code = HttpStatus.CREATED)
     @PostMapping(value = "/order", consumes = MediaType.APPLICATION_JSON_VALUE)
     public void postOrder(OrderDto order) {
           // Implementation codes
     }
...

Note we provide here a brief summary and description of the endpoint. Also, a number of possible responses and their reasons are included using the @ApiResponse annotation in the responses attribute. The above annotation will result in the following OpenAPI fragment

  /order:
    post:
      summary: Create a new order
      description: Use this endpoint to create a new order in the backend
      operationId: postOrder
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderDto'
      responses:
        201:
          description: Success
        403:
          description: Forbidden
        500:
          description: Server Error
      security:
      - myOauth2Security:
        - write

Components

The above API expects an OrderDto object in the request. Springdoc will automatically generate the component element of the object in OpenAPI. It will include all the object properties and their data types. To add more description of the object, we can use the @Schema annotation.

@Schema(
     name = "orderDto",
     description = "Data object for an order",
     oneOf = OrderDto.class
)
public class OrderDto {
...
}

Springdoc also supports JSR-303. For example

public class OrderDto {
     private Long id;
     @NotNull
     private String orderName;
     @NotNull
     private String customerName;
     @NotNull
     private String customerAddress;
     private BigDecimal orderAmount;
     @NotNull
     private List<OrderLineDto> lines;
...

The above will generate the following OpenAPI fragment

components:
  schemas:
    orderDto:
      required:
      - customerAddress
      - customerName
      - lines
      - orderName
      type: object
      description: Data object for an order
      properties:
        id:
          type: integer
          format: int64
        orderName:
          type: string
        customerName:
          type: string
        customerAddress:
          type: string
        orderAmount:
          type: number
        lines:
          type: array
          items:
            $ref: '#/components/schemas/OrderLineDto'

Note the required fields, marked by @NotNull, are defined in the required: elements.

Conclusions

This post has demonstrated how to update a Spring Boot / MVC project to automatically generate OpenAPI specification for the application’s API endpoints. The OpenAPI document can be used by human and computer, for example to generate client codes to consume the API using tools such as the OpenAPI Generator.

For more detailed information about OpenAPI, refer to specification here. The Swagger site also has a section which explains in details the basic structure of an OpenAPI document as well as its individual elements.