Integrating Swagger with Spring Boot REST API
In the last post, I talked about my experience with creating RESTFul Services using Spring Boot. When creating a REST API, proper documentation is a mandatory part of it.
What is Swagger?
Swagger(Swagger 2) is a specification for describing and documenting a REST API. It specifies the format of the REST web services including URL, Resources, methods, etc. Swagger will generate documentation from the application code and handle the rendering part as well.
In this post, I am going to integrate Swagger 2 documentation into a Spring Boot based REST web service. So I am going to use Springfox implementation to generate the swagger documentation. If you want to know how to run/build Spring Boot project, please refer my previous post.
Springfox provides two dependencies to generate API Doc and Swagger UI. If you are not expecting to integrate Swagger UI into your API level, no need to add Swagger UI dependency.
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency>
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>
@ EnableSwagger2 annotation enables Springfox Swagger support in the class. To document the service, Springfox uses a Docket. The Docket helps to configure a subset of the services to be documented and group them by a name, etc. The most hidden concept is that the Springfox works by examining an application at runtime using API semantics based on spring configurations. In other words, you have to create a Spring Java Configuration class which uses spring’s @Configuration
In My example, I am generating a swagger documentation based on the RestController classes I have added.
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class ApplicationConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.chandana.helloworld.controllers")) .paths(PathSelectors.any()) .build(); } }
Since I have added two controllers, this will group(tag) each controller related APIs separately.
Out of the box, Springfox provides five predicates and they are any, none, withClassAnnotation, withMethodAnnotation and basePackage.
ApiInfo
Swagger provides some default values such as “API Documentation”, “Created by Contact Email”, “Apache 2.0”. So you can change these default values by adding apiInfo(ApiInfo apiInfo) method. The ApiInfo class contains custom information about the API.
@Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(getApiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.chandana.helloworld.controllers")) .paths(PathSelectors.any()) .build(); } private ApiInfo getApiInfo() { Contact contact = new Contact("Chandana Napagoda", "http://blog.napagoda.com", "cnapagoda@gmail.com"); return new ApiInfoBuilder() .title("Example Api Title") .description("Example Api Definition") .version("1.0.0") .license("Apache 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") .contact(contact) .build(); }
Once ApiInfo is added, the generated documentation looks similar to this:
Controller and POJO Level Documentation
@Api annotation is used to explain each rest controller class.
@ApiOperation annotation is used to explain to describe the resources and methods.
@ApiResponse annotation is used to explain to describe other responses that can be returned by the operation.ex: 200 ok or 202 accepted, etc.
@ApiModelProperty annotation to describe the properties of the POJO(Bean) class.
After adding above annotation, final generated swagger documentation looks like below:
Spring RestController class:
package com.chandana.helloworld.controllers; import com.chandana.helloworld.bean.Greeting; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") @Api(value = "user", description = "Rest API for user operations", tags = "User API") public class HelloWorldController { @RequestMapping(value = "/hello/{name}", method = RequestMethod.GET, produces = "application/json") @ApiOperation(value = "Display greeting message to non-admin user", response = Greeting.class) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "The resource not found") } ) public Greeting message(@PathVariable String name) { Greeting msg = new Greeting(name, "Hello " + name); return msg; } }
Greeting model class:
package com.chandana.helloworld.bean; import io.swagger.annotations.ApiModelProperty; public class Greeting { @ApiModelProperty(notes = "Provided user name", required =true) private String player; @ApiModelProperty(notes = "The system generated greeting message" , readOnly =true) private String message; public Greeting(String player, String message) { this.player = player; this.message = message; } public String getPlayer() { return player; } public void setPlayer(String player) { this.player = player; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
AppConfig class:
package com.chandana.helloworld.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; 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 ApplicationConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(getApiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.chandana.helloworld.controllers")) .paths(PathSelectors.any()) .build(); } private ApiInfo getApiInfo() { Contact contact = new Contact("Chandana Napagoda", "http://blog.napagoda.com", "cnapagoda@gmail.com"); return new ApiInfoBuilder() .title("Example Api Title") .description("Example Api Definition") .version("1.0.0") .license("Apache 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") .contact(contact) .build(); } }
You can download Swagger Spring Boot Project source code from my GitHub repo as well.
Reference: | Integrating Swagger with Spring Boot REST API from our JCG partner Chandana Napagoda at the Chandana Napagoda blog blog. |
How can you input user/pass if rest api is using either basic o oauth authentication
Please refer “Secure Spring Boot REST API using Basic Authentication” post on this topic.
http://blog.napagoda.com/2017/10/secure-spring-boot-rest-api-using-basic.html
Where does the swagger gets created? does it saves as yalm file?
how can we customize swagger’s default url i.e. /swagger-ui.html to something else? Also if i configure it for any controller in my application, then to access that URL I need to login to my application. I want to access the URL without requiring to Login to my application and later on authenticate my APIs using headers. Can you please help me on that?
Hi, I want to generate Swagger document by annotating the controller interface (not the implementing class), but fail to generate the documentation. Can you please point me to any existing example?
ApiModelProperty readOnly set to true has no expected effect. It does make sense to define it for GET only requests. It should hide property in POST/PUT/PATCH, but in swagger 2.7.0 it does not.