1. Overview

Documentation is an essential part of building REST APIs. In this tutorial, we’ll look at SpringDoc, which simplifies the generation and maintenance of API docs based on the OpenAPI 3 specification for Spring Boot 3.x applications.

2. Setting up springdoc-openapi

Spring Boot 3.x requires to use version 2 of springdoc-openapi:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>

2.1. OpenAPI Description Path

After setting up the dependency correctly, we can run our application and find the OpenAPI descriptions at /v3/api-docs, which is the default path:

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

Further, we can customize the path in application.properties using the springdoc.api-docs property. For example, we can set the path to /api-docs:

springdoc.api-docs.path=/api-docs

Then, we’ll be able to access the docs at:

http://localhost:8080/api-docs

The OpenAPI definitions are in JSON format by default. For yaml format, we can obtain the definitions at:

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

3. Integration with Swagger UI

Besides generating the OpenAPI 3 specification, we can integrate springdoc-openapi with Swagger UI to interact with our API specification and exercise the endpoints.

The springdoc-openapi dependency already includes Swagger UI, so we’re all set to access the API documentation at:

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

3.1. Support for swagger-ui Properties

The springdoc-openapi library also supports swagger-ui properties. These can be used as Spring Boot properties with the prefix springdoc.swagger-ui.

For example, we can customize the path of our API documentation by changing the springdoc.swagger-ui.path property inside our application.properties file:

springdoc.swagger-ui.path=/swagger-ui-custom.html

So now our API documentation will be available at http://localhost:8080/swagger-ui-custom.html.

As another example, we can sort the API paths according to their HTTP methods with the springdoc.swagger-ui.operationsSorter property:

springdoc.swagger-ui.operationsSorter=method

3.2. Sample API

Suppose our application has a controller for managing Books:

@RestController
@RequestMapping("/api/book")
public class BookController {

    @Autowired
    private BookRepository repository;

    @GetMapping("/{id}")
    public Book findById(@PathVariable long id) {
        return repository.findById(id)
            .orElseThrow(() -> new BookNotFoundException());
    }

    @GetMapping("/")
    public Collection<Book> findBooks() {
        return repository.getBooks();
    }

    @PutMapping("/{id}")
    @ResponseStatus(HttpStatus.OK)
    public Book updateBook(
      @PathVariable("id") final String id, @RequestBody final Book book) {
        return book;
    }
}

Then, when we run our application, we can view the documentation at:

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

Swagger UI

Let’s drill down to the /api/book endpoint and see the details for its request and response:
Swagger UI API Details

4. Integrating springdoc-openapi With Spring WebFlux

We can also enable the springdoc.swagger-ui properties on a Spring WebFlux application. This allows easy integration between springdoc-openapi and Swagger UI on our Spring WebFlux application. To enable this, we add the springdoc-openapi-webflux-ui dependency inside our pom.xml file:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>2.1.0</version>
</dependency>

5. Exposing Pagination Information

Spring Data JPA integrates with Spring MVC quite seamlessly. One example of such integration is Pageable support:

@GetMapping("/filter")
public Page<Book> filterBooks(@ParameterObject Pageable pageable) {
     return repository.getBooks(pageable);
}

The support for Pageable has been available out-of-the box since springdoc-openapi v1.6.0. The page, size, and sort query parameters get added to the generated documentation:

Pageable

6. Using springdoc-openapi Maven Plugin

The springdoc-openapi library provides a Maven plugin, springdoc-openapi-maven-plugin, which generates OpenAPI descriptions in JSON and YAML formats.

The springdoc-openapi-maven-plugin plugin works with the spring-boot-maven plugin. Maven runs the openapi plugin during the integration-test phase.

Let’s see how we can configure the plugin in our pom.xml:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.3.3.RELEASE</version>
    <executions>
        <execution>
            <id>pre-integration-test</id>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <id>post-integration-test</id>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-maven-plugin</artifactId>
    <version>1.4</version>
    <executions>
        <execution>
            <phase>integration-test</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

We can also configure the plugin to use custom values:

<plugin>
    <executions>
        .........
    </executions>
    <configuration> 
        <apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl> 
        <outputFileName>openapi.json</outputFileName> 
        <outputDir>${project.build.directory}</outputDir> 
    </configuration>
</plugin>

Let’s take a closer look at the parameters that we can configure for the plugin:

  • apiDocsUrl – URL where the docs can be accessed in JSON format, with a default of http://localhost:8080/v3/api-docs
  • outputFileName – Name of the file where the definitions are stored; defaults to openapi.json
  • outputDir – Absolute path for the directory where the docs are stored; by default, it’s ${project.build.directory}

7. Automatic Document Generation Using JSR-303 Bean Validation

When our model includes JSR-303 bean validation annotations, such as @NotNull, @NotBlank, @Size, @Min, and @Max, the springdoc-openapi library uses them to generate additional schema documentation for the corresponding constraints.

Let’s see an example using our Book bean:

public class Book {

    private long id;

    @NotBlank
    @Size(min = 0, max = 20)
    private String title;

    @NotBlank
    @Size(min = 0, max = 30)
    private String author;

}

Now the documentation generated for the Book bean is a little more informative:
Book Schema After Adding Bean Validation

8. Generate Documentation Using @ControllerAdvice and @ResponseStatus

Using @ResponseStatus on methods in a @RestControllerAdvice class will automatically generate documentation for the response codes. In this @RestControllerAdvice class, the two methods are annotated with @ResponseStatus:

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(ConversionFailedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<String> handleConversion(RuntimeException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
    
    @ExceptionHandler(BookNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<String> handleBookNotFound(RuntimeException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

As a result, we can now see the documentation for the response codes 400 and 404:
Documentation for @ControllerAdvice and @ResponseStatus

9. Generate Documentation Using @Operation and @ApiResponses

Next, let’s see how we can add some description to our API using a couple of OpenAPI-specific annotations.

To do that, we’ll annotate our controller’s /api/book/{id} endpoint with @Operation and @ApiResponses:

@Operation(summary = "Get a book by its id")
@ApiResponses(value = { 
  @ApiResponse(responseCode = "200", description = "Found the book", 
    content = { @Content(mediaType = "application/json", 
      schema = @Schema(implementation = Book.class)) }),
  @ApiResponse(responseCode = "400", description = "Invalid id supplied", 
    content = @Content), 
  @ApiResponse(responseCode = "404", description = "Book not found", 
    content = @Content) })
@GetMapping("/{id}")
public Book findById(@Parameter(description = "id of book to be searched") 
  @PathVariable long id) {
    return repository.findById(id).orElseThrow(() -> new BookNotFoundException());
}

Here’s the effect:

BooksGetByID

As we can see, the text we added to @Operation is placed at the API operation level. Similarly, the description added to various @ApiResponse elements in the @ApiResponses container annotation is also visible here, adding meaning to our API responses.

We do not get any schema for the responses 400 and 404 above. As we defined an empty @Content for them, only their descriptions are displayed.

10. Kotlin Support

Spring Boot 2.x has first-class support for Kotlin. Since we are using version 3.x of Spring Boot, SpringDoc supports applications written in Kotlin.

We’ll create a simple Foo API in Kotlin to see this in action.

After the initial setup, we’ll add a data class and a controller. We’ll add them in a sub-package of our Boot App so that when it’s run, it picks our FooController up along with the earlier BookController:

@Entity
data class Foo(
    @Id
    val id: Long = 0,
    
    @NotBlank
    @Size(min = 0, max = 50)
    val name: String = ""
)

@RestController
@RequestMapping("/")
class FooController() {
    val fooList: List = listOf(Foo(1, "one"), Foo(2, "two"))

    @Operation(summary = "Get all foos")
    @ApiResponses(value = [
    ApiResponse(responseCode = "200", description = "Found Foos", content = [
            (Content(mediaType = "application/json", array = (
                ArraySchema(schema = Schema(implementation = Foo::class)))))]),
    ApiResponse(responseCode = "400", description = "Bad request", content = [Content()]),
    ApiResponse(responseCode = "404", description = "Did not find any Foos", content = [Content()])]
    )
    @GetMapping("/foo")
    fun getAllFoos(): List = fooList
}

Now when we hit our API documentation URL, we’ll see the Foo API as well:

FooAPI kotlin
To enhance the support of Kotlin types, we can add this dependency:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-common</artifactId
    <version>2.1.0</version>
</dependency>

After that, our Foo schema will look more informative, as it did when we added JSR-303 Bean Validation:

FooSchema

11. Conclusion

In this article, we learned to set up springdoc-openapi in our projects. Then, we saw how to integrate springdoc-openapi with the Swagger UI. Finally, we also saw how to do this with Spring Webflux projects.

Next, we used the springdoc-openapi Maven Plugin to generate OpenAPI definitions for our APIs, and we saw how to expose paging and sorting information from Spring Data. After that, we looked at how springdoc-openapi generates documentation automatically using JSR 303 bean validation annotations and the @ResponseStatus annotations in @ControllerAdvice class.

We also learned how to add a description to our API using a few OpenAPI-specific annotations. Finally, we took a peek at OpenAPI’s support of Kotlin.

The springdoc-openapi generates API documentation as per OpenAPI 3 specifications. Moreover, it also handles the Swagger UI configuration for us, making API document generation a reasonably simple task.

As always, the code is available over on GitHub.