Implementing Swagger2 in Spring Boot Application for API Documentation

Swagger2 is an open-source project used to describe, implement and document RESTful APIs. Swagger2 also helps developers to test their back-ends services.

The Swagger2 specification, which is OpenAPI specification, has several implementations. Currently, Springfox’s Swagger2 is most popular. Today, we will discuss and implement Springfox’s Swagger2 for Spring Boot Application version 2.0. We will use embedded Tomcat container and also will make Oracle’s WebLogic compatible.

We will be using:

  1. SpringBoot 2.x
  2. Gradle 4.x or Maven 3.x
  3. Swagger2 v2.9.2
  4. Lombok 1.18.0
  5. WebLogic 12.2.1.3

Please refer to my first article to create and download project from Spring’s Official website.

After downloading the project, let’s start with adding Swagger2 dependencies to our build.gradle

compile('io.springfox:springfox-swagger2:2.9.2')
compile('io.springfox:springfox-swagger-ui:2.9.2')

RESTful Application:

Our application will have a set of REST endpoints to manage and explore users. We will use Java’s List to add/update/delete/traverse the users. We will create service layer composed of UserService interface and it’s implementation class, UserServiceImpl.

Below is our complete build.gradle. We will discuss at last to make this application WebLogic compatible.

buildscript {
   ext {
      springBootVersion = '2.0.3.RELEASE'
   }
   repositories {
        mavenLocal()
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'
apply plugin: 'eclipse-wtp'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'war'
apply plugin: 'idea'

group = 'com.app'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

war{
    baseName = 'boot-swagger2'
    version = '1.0'
}

repositories {
   mavenLocal()
   mavenCentral()
}

configurations {
   providedRuntime
}

dependencies {
   compile('org.springframework.boot:spring-boot-starter-web')
   runtime('org.springframework.boot:spring-boot-devtools')

    compile('org.projectlombok:lombok:1.18.0')

    //Add Swagger2 dependencies
    compile('io.springfox:springfox-swagger2:2.9.2')
   compile('io.springfox:springfox-swagger-ui:2.9.2')

    providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}

We will create UserController which defines all REST API endpoints. The code contents of UserController is:

package com.app.boot.swagger2.controller;

import com.app.boot.swagger2.domain.User;
import com.app.boot.swagger2.service.UserService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/1.0")
@Api(value = "Home Controller", description = "Operations pertaining to Users.")
public class UserController {

    private final UserService userService;

    /**
     *
     * @param userService
     */
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }

    /**
     *
     * @return
     */
    @ApiOperation(value = "Returns all list of users.", response = List.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved list"),
            @ApiResponse(code = 401, message = "You are not authorized to view the resource"),
            @ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
            @ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
    }
    )
    @GetMapping(value = "/getAllUsers", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<List<User>> getAllUsers() {

        return new ResponseEntity<>(userService.getAllUser(), HttpStatus.OK);
    }

    /**
     *
     * @param id
     * @return
     */
    @ApiOperation(value = "Returns User if ID is found.", response = User.class)
    @GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<User> getUserById(@PathVariable("id")
                                               @ApiParam(value = "User ID", required = true) Long id) {

        return new ResponseEntity<>(userService.getUserById(id), HttpStatus.OK);
    }

    /**
     *
     * @param user
     * @return
     */
    @ApiOperation(value = "Stores the user.", response = User.class)
    @PostMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<User> saveUser(
            @ApiParam(value = "User details", required = true) @RequestBody User user) {

        return new ResponseEntity<>(userService.addUser(user), HttpStatus.CREATED);
    }

    /**
     *
     * @param user
     * @return
     */
    @ApiOperation(value = "Updates the user based Id.")
    @PutMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<Void> updateUser(
            @ApiParam(value = "User details", required = true) @RequestBody User user){

        Long id = user.getId();
        userService.updateUser(user, id);

        return new ResponseEntity<>(HttpStatus.OK);
    }

    /**
     *
     * @param id
     * @return
     */
    @ApiOperation(value = "Deletes the user based on given Id.")
    @DeleteMapping(value = "/users/{id}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<Void> deleteUser(
            @ApiParam(value = "User ID", required = true) @PathVariable("id") Long id){

        userService.deleteUser(id);

        return new ResponseEntity<>(HttpStatus.OK);
    }
}

The @RestController annotation marks UserController as a REST API Controller. This annotation was introduced in Spring 4.0. This annotation default annotates @Controller and @ResponseBody. So, don’t need to add two annotations, just use one – @RestController.

The @RequestMapping used at the class level maps request to /1.0 onto UserController. 1.0 is our version number of API for versioning RESTful Web Services.

Configuring Swagger2

We have to create Docket bean to configure Swagger2 in Spring Boot applications. Springfox Docket instance is the primary API configuration which provides convenience way for configuration. We will name this config class as SwaggerConfig under config package. Code for this config looks like:

package com.app.boot.swagger2.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@Profile("!prod")
public class SwaggerConfig {

    @Value("${swagger.api.title}")
    private String title;

    @Value("${swagger.api.description}")
    private String description;

    @Value("${swagger.api.termsOfServiceUrl}")
    private String termsOfServiceUrl;

    @Value("${swagger.api.version}")
    private String version;

    @Value("${swagger.api.controller.basepackage}")
    private String basePackage;

    @Bean
    public Docket swaggerApi() {
        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.basePackage(basePackage))
                .paths(PathSelectors.ant("/**")).build().apiInfo(metaData());
    }

    private ApiInfo metaData() {
        return new ApiInfoBuilder().title(title).description(description).termsOfServiceUrl(termsOfServiceUrl)
                .version(version).build();
    }
}

In this configuration class, we use @EnableSwagger2 annotation which will enables Swagger support for our application. Here, we have to tell Swagger to read our API endpoints from specified base package which is a String.  The apis() and paths() methods are used to filter the controllers and methods. You would also like to add @Profile(“!prod”) which tells Swagger not to configure Swagger for Production environment. You can use Swagger for Production too but you may want to configure some security settings using Spring Security. Swagger is for developer reference not for external clients. 

In the above code, the RequestHandlerSelectors.basePackage will match our com.app.boot.swagger2.controller

Base package to filter the API. Also, we are passing additional filter to generate documentations for the path which is within that controller package.

You can also customize Swagger by providing additional information about your API. See the method metaData() below:

private ApiInfo metaData() {
    return new ApiInfoBuilder().title(title).description(description).termsOfServiceUrl(termsOfServiceUrl)
            .version(version).build();
}

The title, description, termsOfServiceUrl and version will be read from application.properties file. Below is my application.properties:

server.servlet.context-path=/app/api

spring.profiles.active=local

#Swagger Properties
swagger.api.title=App-Swagger2-Demo
swagger.api.description=Swagger Demo
swagger.api.termsOfServiceUrl=http://blog.anishpanthi.net
swagger.api.version=1.0
swagger.api.controller.basepackage=com.app.boot.swagger2.controller

Now, lets run the application. You should be able to see the Swagger UI when you hit http://localhost:8080/app/api/swagger-ui.html. For better explanation, I'm breaking the output into 3 different images.​Now, let's try to relate our code and this screenshot.

1. The values are the basic API Documentation informations. Those are configured in SwaggerConfig.java and the values are being read from application.properties. The controller specific operations are defined under com.app.boot.swagger2.controller. For this demo, we have only one controller class, UserController.java. Have a look on the UserController.java and try to related with the screens shots provided.

2. Name of your controller. 

3. Class level description. 

@Api(description = "Operations pertaining to Users.")

3.1. Short descriptions to let users know what actually you are going to perform when any one hits the URL.

4. The request methods that we are going to support for this application. "/1.0" is class level request mapping url for this controller followed by request method url. You can play around making request.

Now, check GET "/1.0/getAllUsers". Under this operation, you will see multiple things like above screen shot.

5. Any parameter(s) that you are going to supply will appear here. If you check operation GET "/1.0/users/{id}", you will see below screen:

And code for this is:

@ApiOperation(value = "Returns User if ID is found.", response = User.class)
@GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<User> getUserById(@PathVariable("id")
                                           @ApiParam(value = "User ID", required = true) Long id) {

    return new ResponseEntity<>(userService.getUserById(id), HttpStatus.OK);
}

@ApiParam(value = "User ID", required = true) is the statement that you want to consider here.

6. "Try it out" button will allow you to make a request for particular operation.

7. This indicates, in which type you will be getting the response. Consider below code for this where we have defined MediaType as a value for produces:

@GetMapping(value = "/getAllUsers", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

8. These are the response with code and descriptions. We don't need to add it explicitly unless you want to customize the messages. Here, we are Customizing the messages for HttpStatus code.

@ApiResponses(value = {
        @ApiResponse(code = 200, message = "Successfully retrieved list"),
        @ApiResponse(code = 401, message = "You are not authorized to view the resource"),
        @ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
        @ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
}
)

Now, lets move into our last section, where we can see the Models attributes details. Consider below screen shot:

9. Name of your model class defined under "com.app.boot.swagger2.domain", User.java

public class User {

    @ApiModelProperty(notes = "User's ID", position = 0) //default 0
    private Long id;

    @ApiModelProperty(notes = "User's First Name", position = 1)
    private String firstName;

    @ApiModelProperty(notes = "User's Last Name", position = 2)
    private String lastName;

    @ApiModelProperty(notes = "User's Email address.", position = 3)
    private String email;

    @ApiModelProperty(notes = "User's Phone number", position = 4)
    private String phone;

}

Position will let you position the attribute in Swagger.

10. Attributes that you have declared in User.java, See code above.

11. Attributes data type with their short description. See code above.

That's it. Isn't it simple and easy to use? Please drop your thoughts. Let me know if you have any questions or issues. I will try to solve it. 

WebLogic Compatible

Please refer to my this post for making the above application WebLogic compatible. Follow the process and then you are done. But, just take care about version to use. If you want Spring Boot 2.x to be deployed then you have to use WebLogic v12.2.1.3 is the latest version while developing this example. If your Boot's version is 1.5.x, then you have to use the older version of WebLogic 12.1.3. Detail process has been explained in my earlier post.

Complete project is available in Github.

Thanks and Cheers!!!

Logging using Logback in Spring Boot Applications
Integration Testing Using Cucumber-Java