Swagger 2 Integration with Spring REST

This is the continuation of my earlier posting,  Swagger For the REST of Us. Here,  I will cover Springfox integration with my REST Hello World project. Though Springfox supports both 1.2 and 2.0 versions of the Swagger specification, I will stick to Swagger 2.0 specification.

Swagger generates metadata including server host name, endpoint URL, mime-types that the API can consume or produce, data types produced and consumed by operations, etc. Swagger exposes API documentation in JSON format by scanning the source code. Swagger UI is part of the Swagger project and consists of a collection of HTML, Javascript, and CSS resources. Swagger UI takes Swagger compliant JSON API documents as input and dynamically generates an interactive UI of the REST services.

I will continue with the ongoing REST Hello World example. You can find the last version of the example in REST API and Error Handling posting. The example source code mentioned in this posting can be found here.

Changes to Maven Dependencies

  • Add springfox-swagger2 dependency to integrate Swagger Springfox with Spring REST service example.
  • Add springfox-swagger-ui dependency to enable Swagger UI.
<!-- Swagger Spring -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<!-- Swagger UI -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

Configure Docket Configuration Bean

Create a new Docket bean to configure Swagger. A Docket bean is a POJO enabled by @Configuration, @EnableSwagger2, and @Bean annotations. For Swagger 2.0, use @EnableSwagger2 annotation. Here is the example Docket bean:

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

Import the bean by adding the package name (if it’s missing) in the component-scan tag of the existing rest-dispatcher-servlet.xml.
<context:component-scan base-package=
"basaki;basaki.service;basaki.service.controller;
basaki.service.impl;basaki.data" />

Verification of Swagger

Once you have the example running, Swagger documentation in JSON format can be viewed at localhost:8080/rest-swag-simple/api-docs.

0

Enable Swagger UI

Other than the addition of new springfox-swagger-ui dependency in the pom.xml, changes need to be made to the rest-dispatcher-servlet.xml to serve the static UI content of Swagger UI. Here are the changes to rest-dispatcher-servlet.xml:

<!-- Direct static mappings -->
<mvc:resources mapping="swagger-ui.html"
location="classpath:/META-INF/resources/" />
<mvc:resources mapping="/webjars/**"
location="classpath:/META-INF/resources/webjars/" />

Verification of Swagger UI

Once you deploy the example in Tomcat, the Swagger UI can be viewed by visiting http://localhost:8080/rest-springfox/swagger-ui.html in any browser. In my experience, the behavior of Swagger in Internet Explorer is flaky.

swag-1

Once you have access to Swagger UI, you can view all the operations by clicking Show/Hide.

swag-2.png

You can play around with any of the operations by clicking the operation link. Once the operation is expanded, you can retrieve data by clicking Try it out! button. In this example, try out getCustomers operation. It should fetch a list of customers.

swag-3

Further Customization of Controller (Optional)

Use Swagger annotation to make the API more descriptive and hide some of the internal information, e.g., a controller’s method names, etc. Here are some of the Swagger annotations commonly used to document a controller:

  • @Api describes the general responsibility of the controller.
  • @ApiOperation describes the responsibility of a specific method.
  • @ApiParam describes a  method parameter. It also describes whether a parameter is mandatory or not.
@Controller
@Api(value = "customers")
public class CustomerServiceController {
...
@ApiOperation(value = "Gets a customer based on customer id",
notes = "Retrieves a single customer", response = Customer.class)
@RequestMapping(method = RequestMethod.GET,
produces = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE },
value = "customers/{id}")
@ResponseBody
public Customer getCustomer(@PathVariable Integer id) {
Customer customer = service.getCustomer(id);
return customer;
}
...
}

Swagger UI should look more descriptive if you now visit http://localhost:8080/rest-springfox/swagger-ui.html in your browser.

swag-4

56 comments

  1. Good post. Followed step by step and its working, but the problem is that i am not able to change the request URL in the swagger ui .it’s always pointing to the localhost:8080. I have hosted it in the server which has public IP. so finally it should fetch the public ip but it is fetching the localhost ip which makes it difficult to check the services. Help needed.

    Like

      1. Can you please try the latest version of springfox – 2.6.1? I don’t see any problem with one of my other deployments which has a different host name than localhost.

        Like

  2. It’s kind of a hack but try this.

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

    Like

      1. I want know how can we hide the Controller name ex (custom-service-controller) to (Custom Controller). Is there any annotation to rename that. @Api with value and description but it did not reflect).

        Like

      2. When you type ‘hostname’ on your computer terminal, what do you get in return? If it’s ‘localhost’, you need to modify your DNS. How to modify, depends on your OS.

        Like

    1. You need to change the value in ‘tags’ parameter in @Api annotation. Here is an example:

      @Api(value = "Customers API",
      description = "Customer Service",
      produces = "application/json", tags = {"Customer Controller"})

      It should appear in the UI as:

      Api Documentation
      Api Documentation
      Apache 2.0

      Customer Controller: Customer Service Show/Hide| List Operations| Expand Operations

      Liked by 1 person

  3. I have service which takes a requestbody of some model class ex(Users). In this model class i have like 10 attributes. Fo the service i am using only 4 attributes. How can i show only the 4 attributes in swagger UI example value for that service.

    class Users{
    private String userName;
    private String password;
    private String firstName;
    private String lastName;
    private String middleName;
    private String address;
    private String gender;
    private String dob;
    }

    Swagger Ui Exampl Value

    {
    “firstName”: “string”,
    “middleName”: “string”,
    “lastName”: “string”
    }

    Like

    1. You can take advantage of the hidden parameter in @ApiModelProperty annotation. Here is an example:

      @ApiModel
      public class Users{
          private String userName;
          private String firstName;
          private String lastName;
          private String middleName;
      
          @ApiModelProperty(hidden = true)
          public String getUserName() {
              ....
          }
      
          public String getFirstName() {
              ....
          }
      
          public String getLastName() {
              ....
          }
      
          public String getMiddleName() {
              ....
          }
      }
      

      Swagger Ui model should appear like this:

      {
          “firstName”: “string”,
          “middleName”: “string”,
          “lastName”: “string”
      }
      

      Like

      1. Thanks for the reply.

        If the same model class is input for two services and these two require different attributes of the same class

        /getUser accepts json (users object as requestbody)
        {
        “firstName”: “string”,
        “userName”: “string”,
        “lastName”: “string”
        }

        /addUser accepts json (users object as requestbody)
        {
        “firstName”: “string”,
        “middleName”: “string”,
        “lastName”: “string”
        }

        in this scenario how can i display the example value.

        Like

  4. Hi,

    I have multiple rest API’s in a project, r if every api i need to configure swagger separately . So how to make swagger globally for the project and how i use for every api .

    Like

    1. If all your REST controllers are in the same package, only thing you have to do is specify the package during the creation of the Docket. Here is an example of Java configuration:


      @Configuration
      @EnableSwagger2
      public class MySwaggerConfiguration {
      @Bean
      public Docket api() {
      return new Docket(DocumentationType.SWAGGER_2)
      .groupName("myApis")
      .select()
      .apis(exactPackage("com.basaki.example.controller"))
      .paths(PathSelectors.any())
      .build()
      .apiInfo(apiInfo("My Example API", "API Example"));
      }
      private ApiInfo apiInfo(String title, String description) {
      Contact contact = new Contact("Indra Basak", "",
      "developer@gmail.com");
      return new ApiInfo(title, description, "1.0", "terms of service url",
      contact, "license", "license url");
      }
      private static Predicate exactPackage(final String pkg) {
      return input -> declaringClass(input).getPackage().getName().equals(
      pkg);
      }
      private static Class declaringClass(RequestHandler input) {
      return input.declaringClass();
      }
      }

      Like

  5. Hi,

    So if my project have different modules and the RestControllers are placed in different packages, so is there any way scan multiple packages ..for ex : apis(exactPackage(“com.basaki.example.controller”). here we are scan only one package level insted of this want to scan multiple package from single swagger and make it as global for api documentation.

    Like

      1. Hi,

        it’s working fine. But If I integrate with multiple resources are there in project (when integration time of multiple projects) it won’t scan the other resources packages. why because exactPackage() method fetching classes packages from only one resource. Could you please tell me how to scan project specified path level with single swagger configuration.

        Like

      2. it’s working fine. But If I integrate with multiple resources are there in project (when integration time of multiple resources are in projects) it won’t scan the other resources packages. why because exactPackage() method fetching classes packages from only one resource. Could you please tell me how to scan project specified path level with single swagger configuration.

        Like

  6. You can do something like this.


    @Configuration
    @EnableSwagger2
    public class MultiPackageSwaggerConfig {
    @Bean
    public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
    .groupName("menagerie")
    .select()
    .apis(RequestHandlerSelectors.any())
    .paths(PathSelectors.any())
    .build()
    .apiInfo(apiInfo("Menagerie APIs",
    "Exploring Swagger UI Features"));
    }
    private ApiInfo apiInfo(String title, String description) {
    Contact contact = new Contact("Indra Basak", "",
    "developer@gmail.com");
    return new ApiInfo(title, description, "1.0", "terms of controller url",
    contact, "license", "license url");
    }
    }

    Like

  7. is there a way to have a single consolidated UI for different spring boot modules? For ex. i have a Parent project which has like 5 microservices, i want to have a single context where i can show all the apis in a single place.

    Like

    1. Yes, it’s very simple. Just create a Swagger configuration specifying all the controllers. The controller may reside in different modules. In this example, it list all the controllers marked with annotation Api.class.


      @Configuration
      @EnableSwagger2
      public class MultiModuleSwaggerConfiguration {
      @Bean
      public Docket api() {
      return new Docket(DocumentationType.SWAGGER_2)
      .select()
      .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
      .paths(PathSelectors.any())
      .build()
      .apiInfo(apiInfo("Menagerie API",
      "Exploring Swagger UI Features"));
      }
      /**
      * Creates an object containing API information including author name,
      * email, version, license, etc.
      *
      * @param title API title
      * @param description API description
      * @return API information
      */
      private ApiInfo apiInfo(String title, String description) {
      Contact contact = new Contact("Indra Basak", "",
      "developer@gmail.com");
      return new ApiInfo(title, description, "1.0", "terms of controller url",
      contact, "license", "license url", Collections.emptyList());
      }
      }

      Like

  8. hey there..good article..could you show an example when the request body or the response body are of Map type using springfox 2.7

    Like

    1. It shouldn’t be any different from other service methods. Please pay attention to method ‘readMap’ in the ‘TigerController’ example:


      @RestController
      @Slf4j
      @Api(description = "Tiger API", produces = "application/json", tags = {"A2"})
      public class TigerController {
      private static final String TIGER_URL = "/tigers";
      private static final String TIGER_BY_ID_URL = TIGER_URL + "/{id}";
      private static final String HEADER_TXN_ID = "X-TXN-ID";
      private static final String HEADER_TXN_DATE = "X-TXN-DATE";
      private TigerService service;
      @Autowired
      public TigerController(TigerService service) {
      this.service = service;
      }
      @ApiOperation(
      value = "Creates a Tiger.",
      notes = "Requires a Tiger name and gender.",
      response = Tiger.class)
      @ApiResponses({
      @ApiResponse(code = 201, response = Tiger.class,
      message = "Tiger created successfully")})
      @RequestMapping(method = RequestMethod.POST, value = TIGER_URL,
      consumes = MediaType.APPLICATION_JSON_VALUE,
      produces = MediaType.APPLICATION_JSON_VALUE)
      @ResponseStatus(HttpStatus.CREATED)
      public Tiger create(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId,
      @RequestBody MenagerieRequest request) {
      return service.create(request);
      }
      @ApiOperation(
      value = "Retrieves a tiger by ID.",
      notes = "Requires a tiger identifier",
      response = Tiger.class)
      @RequestMapping(method = RequestMethod.GET, value = TIGER_BY_ID_URL,
      produces = {MediaType.APPLICATION_JSON_VALUE})
      @ResponseBody
      public Tiger read(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId,
      @ApiParam(value = "Tiger ID", required = true)
      @PathVariable("id") UUID id) {
      return service.read(id);
      }
      @ApiOperation(
      value = "Retrieves all the tigers associated with the search string.",
      notes = "In absence of any parameter, it will return all the tigers.",
      response = Tiger.class, responseContainer = "List")
      @RequestMapping(method = RequestMethod.GET, value = TIGER_URL,
      produces = {MediaType.APPLICATION_JSON_VALUE})
      @ResponseBody
      public List<Tiger> readAll(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId,
      @ApiParam(value = "Search string to search a tiger based on search criteria. Returns all tigers if empty.")
      @RequestParam(value = "name", required = false) String name,
      @RequestParam(value = "gender", required = false) Gender gender) {
      return service.readAll(name, gender);
      }
      @ApiOperation(
      value = "Retrieves all the tigers associated with the search string.",
      notes = "In absence of any parameter, it will return all the tigers.",
      response = Tiger.class, responseContainer = "List")
      @RequestMapping(method = RequestMethod.GET, value = TIGER_URL + "/map",
      produces = {MediaType.APPLICATION_JSON_VALUE})
      @ResponseBody
      public Map<UUID, Tiger> readMap(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId) {
      return service.readAllAsMap();
      }
      @ApiOperation(value = "Updates a tiger.", response = Tiger.class)
      @ApiResponses({
      @ApiResponse(code = 201, response = Tiger.class,
      message = "Updated a tiger successfully")})
      @RequestMapping(method = RequestMethod.PUT, value = TIGER_BY_ID_URL,
      consumes = MediaType.APPLICATION_JSON_VALUE,
      produces = MediaType.APPLICATION_JSON_VALUE)
      @ResponseStatus(HttpStatus.CREATED)
      public Tiger update(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId,
      @ApiParam(value = "Tiger ID", required = true)
      @PathVariable("id") UUID id,
      @RequestBody MenagerieRequest request) {
      return service.update(id, request);
      }
      @ApiOperation(value = "Deletes a tiger by ID.")
      @RequestMapping(method = RequestMethod.DELETE, value = TIGER_BY_ID_URL)
      @ResponseBody
      public void delete(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId,
      @ApiParam(value = "Tiger ID", required = true)
      @PathVariable("id") UUID id) {
      service.delete(id);
      }
      @ApiOperation(value = "Deletes all tigers.")
      @RequestMapping(method = RequestMethod.DELETE, value = TIGER_URL)
      @ResponseBody
      public void deleteAll(
      @ApiParam(value = "Transaction ID as UUID")
      @RequestHeader(HEADER_TXN_ID) UUID txnId) {
      service.deleteAll();
      }
      }

      Used springfox 2.7.0 version. Here is the response body.


      {
      "a3f70349-1828-495a-b3a2-572719713e5a": {
      "id": "a3f70349-1828-495a-b3a2-572719713e5a",
      "name": "tiger-2",
      "gender": "FEMALE"
      },
      "ce2a3ff5-2cff-4097-befd-80c72cf42c94": {
      "id": "ce2a3ff5-2cff-4097-befd-80c72cf42c94",
      "name": "tiger-1",
      "gender": "MALE"
      }
      }

      view raw

      JsonMapResponse

      hosted with ❤ by GitHub

      Like

  9. Hi Indra, mostly i have followed this example. but i am using LUIS server. and I am getting below error. please share if you have any info on this. thank you.

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping’: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Cannot convert value [com.lloydstsb.luis.jndi.JNDIContext@171e98f {}] from source type [JNDIContext] to target type [String]

    Like

    1. It’s something to do with your @RequestMapping annotation in your controller. It can’t convert of the type your are passing ‘com.lloydstsb.luis.jndi.JNDIContext’ to String. Please post your Controller mapping before I comment any further.

      Like

  10. Hi,

    Am using spring boot REST API, my controller has GET request00:00:00.0000000″. Soam passing path variable”2017-09-08 00:00:00.0000000″ with url. But once the value coming into controller side as a string the it shows only “2017-09-08 00:00:00”, after dot(.) the reaming value as trim and has getting only “2017-09-08 00:00:00”. please suggest me why it happen.

    Like

  11. @RequestMapping(value =”/fetchRecipeMetadataByKeyValue/{keyFilter}/{keyValue}” ,method = RequestMethod.GET , produces = “application/json”)
    public String fetchRecipeMetadataByKeyValue(@PathVariable String keyFilter,@PathVariable String keyValue){

    JSONObject metadata = new JSONObject();
    List recipeMetadataList = new ArrayList();

    metadataList = metadataService.getMetaDataByKey(filterKey, keyValue);
    metadata.put(“MetaData”, metadataList);

    From Swagger Side:–>

    keyFilter = modified_date (data type = string)

    keyValue = 2017-08-31 00:00:00.0000000 (data type =string)

    Like

Leave a reply to indra basak Cancel reply