Best Practice of Spring Boot RestAPI response format with Exception and Error handling in Spring Boot - Organization level coding example of Spring Boot. - NESTED CODE || TECH FLOAT

Breaking

Post Top Ad

Post Top Ad

Saturday, 16 May 2020

Best Practice of Spring Boot RestAPI response format with Exception and Error handling in Spring Boot - Organization level coding example of Spring Boot.


We would be covering exception handling and following best practices to create a valid and uniform response for Spring Boot rest API.

Download the code here.

Let's take a look of the requirement. Below is the sample of json requested by client. This is in case of a valid response.

{
    "message": {
        "body": [
            {
                "productID": 1,
                "productName": "Fofali TestEdit Item",
                "price": "4200"
            },
            {
                "productID": 5,
                "productName": "Fofali Food Item",
                "price": "1200"
            },
            {
                "productID": 7,
                "productName": "Fofali Fashion Item",
                "price": "4500"
            }
        ],
        "status": "OK"
    },
    "error": null
}

Below JSON format expected in case of any invalid (exception) response.

{
    "message": null,
    "error": {
        "errorBody": {
            "errorMessage": "resource not found",
            "errorCode": "NOT_FOUND",
            "url": "ServletWebRequest: uri=/error;client=0:0:0:0:0:0:0:1"
        },
        "errorCode": "NOT_FOUND"
    }
}

So, basically, "message" tag will hold actual data in case of valid response and "error" tag will hold error information in case of any exception or bad request.

We will be modifying the same project created in our previous blog and create a "Product" api which will have GET, POST, PUT and DELETE end point.


First of all modify the Product model. Here ProductID is auto generated and primary key also   Product name and price can not be blank. Product name also should be unique, cause we should not allow any duplicate products.

 Below is the sample code of Product.java

package com.ncteam.weblogic.bean;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@SuppressWarnings("serial")
@Entity(name="NC_PRODUCT")
@Table(
  
   uniqueConstraints = {@UniqueConstraint(columnNames = {"productID", "productName"})}
)
public class Product implements  Serializable{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int productID;
@NotBlank(message="Product Name is Mandetory")
@Size(min=4, message="Product Name should have atleast 2 characters")
@Column(unique = true)
private String productName;
@NotBlank(message="Price is Mandetory")
private String price;

public Product() {

}

public Product(int productID, String productName, String price) {
super();
this.productID = productID;
this.productName = productName;
this.price = price;
}
public int getProductID() {
return productID;
}
public void setProductID(int productID) {
this.productID = productID;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}


}

Update previous version of service class with below mentioned code. Code for ProductService.java

package com.ncteam.weblogic.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ncteam.weblogic.bean.Product;
import com.ncteam.weblogic.dao.ProductDAO;
@Service
public class ProductService {
@Autowired
private ProductDAO productDAO;

public List<Product> getAllProduct(){
return (List<Product>) productDAO.findAll();
}
public Product saveAllProduct(Product product) {
// TODO Auto-generated method stub
return productDAO.save(product);
}
public Optional<Product> getAllProductById(int id) {
// TODO Auto-generated method stub
return productDAO.findById(id);
}
public Product editProduct(int id, Product product) {
// TODO Auto-generated method stub
product.setProductID(id);
return productDAO.save(product);
}
public List<Product> deleteProductById(int id) {
// TODO Auto-generated method stub
productDAO.deleteById(id);
return (List<Product>) productDAO.findAll();
}

}

Below is the DAO class used for this project which is same as previous blog.

package com.ncteam.weblogic.dao;
import org.springframework.data.repository.CrudRepository;
import com.ncteam.weblogic.bean.Product;
public interface ProductDAO extends CrudRepository<Product, Integer>{
}


Now create ReturnModel.java bean class to format response model as expected by client's json version.

package com.ncteam.weblogic.returnmodel;
import java.util.Map;
public class ReturnModel {
private Map message;

private Map error;
public ReturnModel() {

}

public ReturnModel(Map message, Map error) {
super();
this.message = message;
this.error = error;
}
public Map getMessage() {
return message;
}
public void setMessage(Map message) {
this.message = message;
}
public Map getError() {
return error;
}
public void setError(Map error) {
this.error = error;
}
}

Let's start working on exception related changes. First of all we need a model pojo class which will have basic exception data. ExceptionModel.java

package com.ncteam.weblogic.exceptionpkg;
import org.springframework.http.HttpStatus;
public class ExceptionModel {
private String errorMessage;
private HttpStatus errorCode;
private String url;

public ExceptionModel() {

}

public ExceptionModel(String errorMessage, HttpStatus errorCode, String url) {
super();
this.errorMessage = errorMessage;
this.errorCode = errorCode;
this.url = url;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public HttpStatus getErrorCode() {
return errorCode;
}
public void setErrorCode(HttpStatus internalServerError) {
this.errorCode = internalServerError;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}


}
Lets create below mention two custom user define exception class

package com.ncteam.weblogic.exceptionpkg;
public class PageNotFoundException extends Exception {
public PageNotFoundException() {
// TODO Auto-generated constructor stub
}
public PageNotFoundException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public PageNotFoundException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public PageNotFoundException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public PageNotFoundException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
}

package com.ncteam.weblogic.exceptionpkg;

public class UserNotFoundException extends Exception {
public UserNotFoundException() {
// TODO Auto-generated constructor stub
}
public UserNotFoundException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public UserNotFoundException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public UserNotFoundException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
}

Now create the controller class to handle all exceptions as per origination requirement .

package com.ncteam.weblogic.exceptionpkg;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.ncteam.weblogic.returnmodel.ReturnModel;
@ControllerAdvice
@RestController
public class ExceptionController extends ResponseEntityExceptionHandler
implements ErrorController{
@SuppressWarnings("unchecked")
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request){
ExceptionModel model = new ExceptionModel(ex.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR,request.toString());
Map errorMap = new HashMap<>();
errorMap.put("errorBody", model);
errorMap.put("errorCode", HttpStatus.INTERNAL_SERVER_ERROR);
ReturnModel returnModel = new ReturnModel();
returnModel.setError(errorMap);
return new ResponseEntity<Object>(returnModel,HttpStatus.INTERNAL_SERVER_ERROR);
}
@SuppressWarnings("unchecked")
@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundException(Exception ex, WebRequest request){
ExceptionModel model = new ExceptionModel(ex.getMessage(),HttpStatus.NOT_FOUND,request.toString());
Map errorMap = new HashMap<>();
errorMap.put("errorBody", model);
errorMap.put("errorCode", HttpStatus.NOT_FOUND);
ReturnModel returnModel = new ReturnModel();
returnModel.setError(errorMap);
return new ResponseEntity<Object>(returnModel,HttpStatus.NOT_FOUND);
}
//DataIntegrityViolationException
@SuppressWarnings("unchecked")
@ExceptionHandler(DataIntegrityViolationException.class)
public final ResponseEntity<Object> handlePKeyException(Exception ex, WebRequest request){
ExceptionModel model = new ExceptionModel(ex.getMessage(),HttpStatus.CONFLICT,request.toString());
Map errorMap = new HashMap<>();
errorMap.put("errorBody", model);
errorMap.put("errorCode", HttpStatus.CONFLICT);
ReturnModel returnModel = new ReturnModel();
returnModel.setError(errorMap);
return new ResponseEntity<Object>(returnModel,HttpStatus.CONFLICT);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionModel model = new ExceptionModel(ex.getMessage(),HttpStatus.METHOD_NOT_ALLOWED,request.toString());
Map errorMap = new HashMap<>();
errorMap.put("errorBody", model);
errorMap.put("errorCode", HttpStatus.METHOD_NOT_ALLOWED);
ReturnModel returnModel = new ReturnModel();
returnModel.setError(errorMap);
return new ResponseEntity<Object>(returnModel,HttpStatus.METHOD_NOT_ALLOWED);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionModel model = new ExceptionModel(ex.getMessage(),HttpStatus.METHOD_NOT_ALLOWED,request.toString());
Map errorMap = new HashMap<>();
errorMap.put("errorBody", model);
errorMap.put("errorCode", HttpStatus.METHOD_NOT_ALLOWED);
ReturnModel returnModel = new ReturnModel();
returnModel.setError(errorMap);
return new ResponseEntity<Object>(returnModel,HttpStatus.METHOD_NOT_ALLOWED);
}
@SuppressWarnings("unchecked")
@ExceptionHandler(PageNotFoundException.class)
public final ResponseEntity<Object> handlePageNotFoundException(Exception ex, WebRequest request){
ExceptionModel model = new ExceptionModel(ex.getMessage(),HttpStatus.NOT_FOUND,request.toString());
Map errorMap = new HashMap<>();
errorMap.put("errorBody", model);
errorMap.put("errorCode", HttpStatus.NOT_FOUND);
ReturnModel returnModel = new ReturnModel();
returnModel.setError(errorMap);
return new ResponseEntity<Object>(returnModel,HttpStatus.NOT_FOUND);
}
@GetMapping(value = "/error")
    public void handleError(HttpServletRequest request) throws PageNotFoundException {        
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        if (status != null) {        
            Integer statusCode = Integer.valueOf(status.toString());
            if(statusCode == HttpStatus.NOT_FOUND.value()) {
            throw new PageNotFoundException("resource not found");
             }
           
        }       
    }
@Override
public String getErrorPath() {
// TODO Auto-generated method stub
return "/error";
}
}

Few important points on the above exception controller class

  • ResponseEntityExceptionHandler should be extended which will allow us to override all the ready made exception method provided by Spring framework.
  • ErrorController should be implemented to edit the error page response.
  • @ExceptionHandler(Exception.class) should be used to control exception or catch user define exception.
Project structure should be like below image:



That's all, time to execute the project and test all the endpoints.

Success Get response:



Exception in case of not found.



Like this you could check all other endpoints. Comment below in case of any exception.

No comments:

Post a Comment

Post Bottom Ad