Documenting Spring Boot REST API with Swagger

Documentation is one important aspect for any application. Especially for REST API, good documentation is very important — even instrumental for external adoption or partners acquisitions. The documentation must be up-to-date, every change in the API should be simultaneously described in the reference documentation. Like good code, good documentation is difficult and time consuming to write. Accomplishing this manually is (very) tedious, and as developer we love to automate it.

In this tutorial, we will look at setting up Swagger and and SpringFox to create REST API documentation in Spring Boot application. Let see it in action!

What is Swagger?

Swagger is a specification for documenting REST API. It specifies the format (URL, method, and representation) to describe REST web services. The goal is to enable the service producer to update the service documentation in real time so that client (consumer) can get up-to-date information about the service structure (request/response, model, etc). With swagger, documentation systems are moving at the same pace as the server because all methods, parameters, and models description are tightly integrated into the server code, thereby maintaining the synchronization in APIs and its documentation.

Swagger is very helpful for automating the documentation of your APIs, and I always using it for every Spring API Projects. For this article, you'll need a Spring Boot application with Rest Controller(s). We already have one from our previous tutorial.

Swagger Dependencies

For automated JSON API documentation for API's built with Spring we will use SpringFox dependency (version 2.9.2 per June 2018). SpringFox 2.9.2 use version 2 of the Swagger specification. If you are using Maven, you can add it as a dependency as the following:

<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency>

Swagger Configuration

The configuration for Swagger is minimal. It centers around the Docket bean, which is the main bean used to configure SpringFox. We need to add the below configuration in a configuration class.

SwaggerConfig.java
package com.dariawan.contactapp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {                                    
    @Bean
    public Docket api() { 
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}
                    

  • @Configuration annotation indicates that the class has @Bean definition, and tells Spring to scan this class and wire up the bean in the context.
  • @EnableSwagger2 annotation is used to enable the Swagger2 for your Spring Boot application .
  • We create a Docket bean and annotate it with @Bean.
  • DocumentationType.SWAGGER_2 tells the Docket bean that we are using version 2 of Swagger specification.
  • The Docket bean’s select() creates a builder (an instance of ApiSelectorBuilder) to control the end-points that are exposed by Swagger. This is achieved by defining which controllers and which of their methods should be included in the generated documentation.
  • ApiSelectorBuilder's apis() defines the classes (controller and model classes) to be included. Request Handlers can be configured using RequestHandlerSelectors and PathSelectors.
  • ApiSelectorBuilder's paths() allow you to define which controller's methods should be included based on their PathSelectors (path mappings).

In our sample above we include all by using any() for both. It will enable the entire API to be available for Swagger, but you can limit them by a base package, class annotations and more.

This configuration is enough to enable Swagger. We can verify it by start the application and visit the URL http://localhost:8080/v2/api-docs:

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

Swagger's API Docs

Add Swagger UI

From http://localhost:8080/v2/api-docs we can see that swagger metadata describing your API is already being generated, but for us is not very human readable. Swagger UI is a built-in solution which makes user interaction with the Swagger-generated API documentation much easier. To use Swagger UI, one additional dependency is required, as example for Maven:

<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>

The documentation will be automatically available in http://localhost:8080/swagger-ui.html:

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

Swagger UI

Customize APIInfo

The default API Info as shown in our swagger-ui.html is quite generic:

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

Swagger UI

To change the default API Info, we need to add it in our SwaggerConfig:

package com.dariawan.contactapp.config;

import java.util.Collections;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {                                    
    
    @Bean
    public Docket api() { 
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build().apiInfo(getApiInfo());                                           
    }

    private ApiInfo getApiInfo() {
        return new ApiInfo(
                "Contact Application API",
                "This is a sample Spring Boot RESTful service using SpringFox + Swagger 2",
                "V1",
                "urn:tos",
                new Contact("Dariawan", "https://www.dariawan.com", "hello@dariawan.com"),
                "CC BY-SA 3.0",
                "https://creativecommons.org/licenses/by-sa/3.0/",
                Collections.emptyList()
        );
    }
}
                    

And the result is as following:

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

Customized APIInfo in Swagger UI

Using Swagger-Core Annotations

In order to generate the Swagger documentation, swagger-core offers a set of annotations to declare and manipulate the output. Here a list of most used of Swagger-Core annotations:

NameDescription
@ApiMarks a class as a Swagger resource.
@ApiModelProvides additional information about Swagger models.
@ApiModelPropertyAdds and manipulates data of a model property.
@ApiOperationDescribes an operation or typically a HTTP method against a specific path.
@ApiParamAdds additional meta-data for operation parameters.
@ApiResponseDescribes a possible response of an operation.
@ApiResponsesA wrapper to allow a list of multiple ApiResponse objects.

For more annotations, please refer to Swagger-Core Annotations documentation.

The following example is to show how to annotate your controllers and their methods and method parameters.

package com.dariawan.contactapp.controller;

...
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
...

@Api(description = "Endpoints for Creating, Retrieving, Updating and Deleting of Contacts.",
        tags = {"contact"})
@RestController
@RequestMapping("/api")
public class ContactController {
    
    ...
    
    @ApiOperation(value = "Find Contacts by name", notes = "Name search by %name% format", tags = { "contact" })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "successful operation", response=List.class )  })	    
    @GetMapping(value = "/contacts")
    public ResponseEntity<List<Contact>> findAll(
            @ApiParam("Page number, default is 1") @RequestParam(value="page", defaultValue="1") int pageNumber,
            @ApiParam("Name of the contact for search.") @RequestParam(required=false) String name) {
        ...
	return ...
    }

    @ApiOperation(value = "Find contact by ID", notes = "Returns a single contact", tags = { "contact" })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "successful operation", response=Contact.class),
        @ApiResponse(code = 404, message = "Contact not found") })
    @GetMapping(value = "/contacts/{contactId}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Contact> findContactById(
            @ApiParam("Id of the contact to be obtained. Cannot be empty.")
            @PathVariable long contactId) {
        ...
	return ...
    }
    
    @ApiOperation(value = "Add a new contact", tags = { "contact" })
    @ApiResponses(value = { 
        @ApiResponse(code = 201, message = "Contact created"), 
        @ApiResponse(code = 400, message = "Invalid input"), 
        @ApiResponse(code = 409, message = "Contact already exists") })	    
    @PostMapping(value = "/contacts")
    public ResponseEntity<Contact> addContact(
            @ApiParam("Contact to add. Cannot null or empty.")
            @Valid @RequestBody Contact contact) 
            throws URISyntaxException {
        ...
	return ...
    }
    
    @ApiOperation(value = "Update an existing contact", tags = { "contact" })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "successful operation"),
        @ApiResponse(code = 400, message = "Invalid ID supplied"),
        @ApiResponse(code = 404, message = "Contact not found"),
        @ApiResponse(code = 405, message = "Validation exception") })
    @PutMapping(value = "/contacts/{contactId}")
    public ResponseEntity<Contact> updateContact(
            @ApiParam("Id of the contact to be update. Cannot be empty.")
            @PathVariable long contactId,
            @ApiParam("Contact to update. Cannot null or empty.")
            @Valid @RequestBody Contact contact) {
        ...
	return ...
    }
    
    @ApiOperation(value = "Update an existing contact's address", tags = { "contact" })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "successful operation"),
        @ApiResponse(code = 404, message = "Contact not found") })
    @PatchMapping("/contacts/{contactId}")
    public ResponseEntity<Void> updateAddress(
            @ApiParam("Id of the contact to be update. Cannot be empty.")
            @PathVariable long contactId,
            @ApiParam("Contact's address to update.")
            @RequestBody Address address) {
        ...
	return ...
    }
    
    @ApiOperation(value = "Deletes a contact", tags = { "contact" })
    @ApiResponses(value = { 
        @ApiResponse(code = 200, message = "successful operation"),
        @ApiResponse(code = 404, message = "Contact not found") })
    @DeleteMapping(path="/contacts/{contactId}")
    public ResponseEntity<Void> deleteContactById(
            @ApiParam("Id of the contact to be delete. Cannot be empty.")
            @PathVariable long contactId) {
        ...
	return ...
    }
}
                    

When we run our application again, our documentation also contains the descriptions that we just provided:

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

swagger-core annotation in endpoint (Controller)

We also can annotate our model classes with Swagger core annotations to provide additional metadata (info):

package com.dariawan.contactapp.domain;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
...

@ApiModel(description = "Class representing a contact in the application.")
@Entity
@Table(name = "contact")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Getter
@Setter
public class Contact implements Serializable {

    private static final long serialVersionUID = 4048798961366546485L;

    @ApiModelProperty(notes = "Unique identifier of the Contact.", 
            example = "1", required = true, position = 0)
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    
    @ApiModelProperty(notes = "Name of the contact.", 
            example = "Jessica Abigail", required = true, position = 1)
    @NotBlank
    private String name;
    
    @ApiModelProperty(notes = "Phone number of the contact.", 
            example = "62482211", required = false, position = 2)
    private String phone;
    
    @ApiModelProperty(notes = "Email address of the contact.", 
            example = "jessica@ngilang.com", required = false, position = 3)
    private String email;
    
    @ApiModelProperty(notes = "Address line 1 of the contact.", 
            example = "888 Constantine Ave, #54", required = false, position = 4)
    private String address1;
    
    @ApiModelProperty(notes = "Address line 2 of the contact.", 
            example = "San Angeles", required = false, position = 5)
    private String address2;
    
    @ApiModelProperty(notes = "Address line 3 of the contact.", 
            example = "Florida", required = false, position = 6)
    private String address3;
    
    @ApiModelProperty(notes = "Postal code of the contact.", 
            example = "32106", required = false, position = 7)
    private String postalCode;
    
    @ApiModelProperty(notes = "Notes about the contact.", 
            example = "Meet her at Spring Boot Conference", required = false, position = 8)
    @Column(length = 4000)
    private String note;    
}
                    

And the result as in updated documentation:

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

swagger-core annotation in Model

NumberFormatException in io.swagger.models.parameters.AbstractSerializableParameter

You may encounter this warning when accessing your swagger-ui.html (swagger documentation):

2019-10-30 00:27:35.043 WARN 15008 --- [nio-8080-exec-4] i.s.m.p.AbstractSerializableParameter : Illegal DefaultValue 1 for parameter type integer java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_92] at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_92] at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_92] at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_92] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_92] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_92] at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688) [jackson-databind-2.9.9.3.jar:2.9.9.3]

As workaround, I disabled the logger for AbstractSerializableParameter class:

logging.level.io.swagger.models.parameters.AbstractSerializableParameter: ERROR

swagger-spring-boot-starter

Another options to add dependencies instead of add springfox-swagger2, springfox-swagger-ui, and springfox-bean-validators is to add one single "bundled" dependency. There are many options out there, but one of them is swagger-spring-boot-starter, which already already include the latest version of SpringFox 2.9.2. Here to add in maven:

<dependency> <groupId>com.spring4all</groupId> <artifactId>swagger-spring-boot-starter</artifactId> <version>1.9.0.RELEASE</version> </dependency>

For springfox-bean-validators, please refer to next tutorial: SpringFox Bean Validators for Swagger Documentation.

Conclusion

Yes, it's really easy to integrate Swagger in Spring Boot projects.

Until version 2.9.2, SpringFox only support version 2 of Swagger (or OpenAPI) specification. To use version 3, we need to switch to another library. Please check next article: Documenting Spring Boot REST API with SpringDoc + OpenAPI 3.