Spring Boot

Spring boot Swagger 2 example

Developing a REST API is hard. 

Not only because of the effort required to design and implementation, but also the effort required for documentation so that the developers who are going to use it have a clear understanding.

Swagger is a framework that helps in the documentation of APIs.

Therefore, In this tutorial, we will see how to document and test our REST services with Swagger

What is Swagger?

If you are looking for a framework to document your API, swagger is what you are looking for.

Swagger is a framework to document and visualize Rest APIs from very different sources.

Swagger 2 demo
Visualize REST API using Swagger 2

Swagger supports many frameworks including Node.js, Grails, Scala Play, Spring Boot, Symfony.

Before starting a demo, let’s me tell you what environment I’m using here to create this tutorial

Environment Details

This demo is developed using the following environment:

Hardware: i7 8650U CPU with 16 GB of RAM
Operating System: Windows 10
IDE: Eclipse
Swagger 2
and Spring Boot

Add Swagger 2 Dependencies in Spring Boot

SpringFox is a popular implementation for Swagger 2 specification. SpringFox supports both Swagger 1.2 and 2.

Let’s add Springfox dependency in pom.xml to bring it in our project.

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

In addition to Springfox, we need to add the dependency for swagger-ui. Let’s add springfox-swagger-ui in the pom file

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

Final pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath /> <! – lookup parent from repository – >
	</parent>
	<groupId>com.codedelay</groupId>
	<artifactId>springboot-swagger-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-swagger-demo</name>
	<description>This is Spring boot Swagger 2 tutorial</description>

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Create REST APIs

Let’s create a CRUD API’s for this demo.

For this tutorial, we will expose a few REST APIs for a car showroom.

POST /api/car/add is to add the car into the car inventory database.

GET /api/car/get/{carId} is to get car details from the inventory. To retrieve car details you have to provide car id.

PUT /api/car/update/{carId}/{price:.+} is to update car’s price. To update the price of the car you have to provide id and updated price of the car.

DELETE /api/car/delete/{carId} is to delete car detail from the inventory.

Steps to create REST APIs in spring boot.

1.) Create an Entity class to define the table structure.

2.) Create a controller class to create and expose the REST apis.

3.) Create a service class that will act as a bridge between dao (repository) and controller.

4.) A repository interface that will extends Spring Data JPA’s CrudRepository interface.

Entity Class (Car.java)

package com.codedelay.swagger.tutorial.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "car_table")
public class Car {
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "id")
	@Id
	private Integer carId;
	
	@Column(name = "brand_name")
	private String brandName;
	
	@Column(name = "model_name")
	private String modelName;
	
	private Double price;

	public Integer getCarId() {
		return carId;
	}

	public void setCarId(Integer bicycleId) {
		this.carId = bicycleId;
	}

	public String getBrandName() {
		return brandName;
	}

	public void setBrandName(String brandName) {
		this.brandName = brandName;
	}

	public String getModelName() {
		return modelName;
	}

	public void setModelName(String modelName) {
		this.modelName = modelName;
	}

	public Double getPrice() {
		return price;
	}

	public void setPrice(Double price) {
		this.price = price;
	}
}

Controller Class (CarController.java)

As you could see in the controller class that we have exposed few APIs for adding a car, finding a car by ID, updating a car and deleting a car by ID.

package com.codedelay.swagger.tutorial.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.codedelay.swagger.tutorial.entity.Car;
import com.codedelay.swagger.tutorial.service.CarService;

@RestController
@RequestMapping("/api/car")
public class CarController {

	@Autowired
	private CarService mService;

	@PostMapping(value = "/add")
	public Car addCar(@RequestBody Car bicycle) {
		return mService.addCar(bicycle);
	}

	@GetMapping(value = "/get/{carId}")
	public Car findCarByID(@PathVariable("carId") Integer carId) {
		return mService.findCarByID(carId);
	}

	@PutMapping(value = "/update/{carId}/{price:.+}")
	public Car updateCarPrice(@PathVariable("carId") Integer carId,
			@PathVariable("price") double updatedPrice) {
		return mService.updateCarPrice(carId, updatedPrice);
	}

	@DeleteMapping(value = "/delete/{carId}")
	public void deleteCar(@PathVariable("carId") Integer carId) {
		mService.deleteCar(carId);
	}
}

Service Class (CarService.java)

The service class acts as a bridge between the repository and the controller.

Service class gets the save request from the controller and it passes the same save the request to the repository and returns the result.

package com.codedelay.swagger.tutorial.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.codedelay.swagger.tutorial.dao.CarRepository;
import com.codedelay.swagger.tutorial.entity.Car;

@Service
public class CarService {
	@Autowired
	private CarRepository mRepository;

	public Car addCar(Car car) {
		return mRepository.save(car);
	}

	public Car findCarByID(Integer carId) {
		return mRepository.findById(carId).get();
	}

	public Car updateCarPrice(Integer carId, double updatedPrice) {
		Car temp =  mRepository.findById(carId).get();
		temp.setPrice(updatedPrice);
		return mRepository.save(temp);
	}

	public void deleteCar(Integer carId) {
		mRepository.deleteById(carId);
	}
}

Repository Interface (CarRepository.java)

Crudrepository is is the main interface in spring data jpa that allows us to write simple crud operations without writing a single line of code

package com.codedelay.swagger.tutorial.dao;

import org.springframework.data.repository.CrudRepository;

import com.codedelay.swagger.tutorial.entity.Car;

public interface CarRepository extends CrudRepository<Car, Integer> {
}

How to handle Error’s for RESTful APIs

Error Handling in REST APIs

A good REST API’s implementation should have proper error handling.

As you could see our current implementation doesn’t have code logic to handle error requests.

But still, spring boot provides the proper message when an error occurred.

Let’s hit the wrong request to see what kind of error messages we are getting now.

GET localhost:8080/api/cars/get

The response of the above request would be:

{
    "timestamp": "2019-07-26T06:42:49.991+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/api/cars/get/"
}
REST API's error - 404
REST API’s error – 404

From the above response from the spring boot, it is clear that we got a 404 response that means the server unable to find the requested resource.

How to customize the error message

As we are trying to customize error response for 404 error, let’s start by writing a custom exception called ResourceNotFoundError.

package com.codedelay.swagger.tutorial.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value  = HttpStatus.NOT_FOUND)
public class ResourceNotFoundError extends Exception {
	private static final long serialVersionUID = 1L;

	public ResourceNotFoundError(String errorMsg) {
		super(errorMsg);
	}
}

As you could see ResourceNotFoundError is extending exception class and has one parameterized constructor.

Now let’s create ErrorDetail.java class and declare all the properties that we want to show in the custom error message.

package com.codedelay.swagger.tutorial.exception;

import java.util.Date;

public class ErrorDetail {
	private Date timestamp;
	private String errorMessage;
	private String errorDetails;

	public ErrorDetail(Date timestamp, String errorMessage, String errorDetails) {
		super();
		this.timestamp = timestamp;
		this.errorMessage = errorMessage;
		this.errorDetails = errorDetails;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(Date timestamp) {
		this.timestamp = timestamp;
	}

	public String getErrorMessage() {
		return errorMessage;
	}

	public void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}

	public String getErrorDetails() {
		return errorDetails;
	}

	public void setErrorDetails(String errorDetails) {
		this.errorDetails = errorDetails;
	}
}

As you could see above we have defined timestamp, errorMessage, and errorDetails properties which will be shown in the custom error message.

Now let’s create a main exception handler. we are annotating this class with @ControllerAdvice so that exception handling will be applied globally for all controller automatically.

package com.codedelay.swagger.tutorial.exception;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class MainExceptionHandler {
	@ExceptionHandler(ResourceNotFoundError.class)
	 public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundError ex, WebRequest request) {
	  ErrorDetail errorDetail = new ErrorDetail(new Date(), ex.getMessage(), request.getDescription(false));
	  return new ResponseEntity<>(errorDetail, HttpStatus.NOT_FOUND);
	 }
	 @ExceptionHandler(Exception.class)
	 public ResponseEntity<?> globleExcpetionHandler(Exception ex, WebRequest request) {
		 ErrorDetail errorDetail = new ErrorDetail(new Date(), ex.getMessage(), request.getDescription(false));
	  return new ResponseEntity<>(errorDetail, HttpStatus.INTERNAL_SERVER_ERROR);
	 }
}

In the MainExceptionHandler class, we have created two methods resourceNotFoundException and globleExcpetionHandler.

If you notice both methods are annotated with @ExceptionHandler.

@ExceptionHandler provides the mechanism to treat exceptions that are thrown during the execution of controller operations.

Configure Swagger 2 in Spring Boot Application.

We have already added Swagger 2 dependencies earlier.

Let’s configure Swagger 2 now.

To configure Swagger 2, we will create a Docket bean in a Configuration file.

The docket is a builder pattern provided in the springfox framework that creates an interface between swagger and spring framework.

In the below class we have enabled the Swagger 2 by using @EnableSwagger2.

In addition to that, we have also provided controller base package details, API’s base URL, license details, etc.

package com.codedelay.swagger.tutorial.config;

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 SwaggerConfig {
	public Docket productApi() {
		return new Docket(DocumentationType.SWAGGER_2)
                .select()                 .apis(RequestHandlerSelectors.basePackage("com.codedelay.helloworld.controller"))
                .paths(PathSelectors.regex("/api.*"))
                .build().apiInfo(carApiInfo());
	}
	
	 private ApiInfo carApiInfo() {
	        return new ApiInfoBuilder().title("Spring Boot REST API")
	            .description("Car Inventory REST API")
	            .contact(new Contact("Arpit", "https://codedelay.com", "contact@codedelay.com"))
	            .license("MIT")
	            .licenseUrl("https://opensource.org/licenses/MIT")
	            .version("0.0.1")
	            .build();
	    }
}

Now let’s run the application and hit http://localhost:9000/swagger-ui.html#/ URL.

swagger spring boot demo
Swagger Demo

Swagger has visualized our Car Controller API’s.

Let’s add a few annotations to the controller class to make this visualization more informative.

@RequestMapping("/api/car")
@Api(value = "Car Inventory Management", protocols = "http")
public class CarController {
@PostMapping(value = "/add")
	@ApiOperation(value = "Save a car into the Car Inventory", response = Car.class, 
			code = 200, /* hidden = true, */ notes = "Don't include carId in the request body")
	public Car addCar(@RequestBody Car car) {
		return mService.addCar(car);
	}
@GetMapping(value = "/get/{carId}")
	@ApiResponses(value = {
	        @ApiResponse(code = 200, message = "Successfully found the car by id"),
	        @ApiResponse(code = 401, message = "You are not authorized to view the car inventory"),
	        @ApiResponse(code = 403, message = "Accessing the car inventory you were trying to reach is forbidden"),
	        @ApiResponse(code = 404, message = "The car detail you were trying to reach is not found")
	})
	public Car findCarByID(@PathVariable("carId") Integer carId) {
		return mService.findCarByID(carId);
	}

In the car controller and addCar method, I have added @Api, @ApiOperation, and @ApiResponse to make APIs documentation more informative.

Restart the application to see the updated result.

 Swagger Informative Documentation
Swagger Informative Documentation
Swagger Add api
Swagger Add api
Swagger Get Response
Swagger Get Response

Conclusion

In this tutorial, we have seen that how Swagger 2 can be used to visualize REST API’s using Spring boot, Swagger 2 and SpringFox.

Click to rate this post!
[Total: 0 Average: 0]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.