Spring Boot Exception Handling – Complete Tutorial

Spring Boot Exception Handling

In this article of Spring Boot Exception Handling, we will learn all about Exception Handling in Spring Boot for the Rest APIs.

We will learn How to validate the request data sent to the REST APIs PUT , POST methods. We will also learn How to add the custom error messages , in case of validation errors , in response.

So Let’s get started :

Introduction

When we are working with enterprise applications, Handling exceptions and errors becomes crucially important. We also need to send the proper responses in case of any validation errors.

We will cover below mentioned two validation cases in our below Spring Boot Exception Handling Example :

  • First, we will send the Invalid Id in the Http Get request /students/{id} And It will return the Http 404 status code with the proper message in response body.
  • Second, we will send the request Http Post /students with request body containing invalid values or missing values And It will return Http 400 status code with the proper message in Response Body.

Rest API Request Validation Annotations

There are many annotations used for validating the request data in Rest Apis.

Let’s look at some important validation annotations :

AnnotationsDescription
@AssertFalseThis annotation validate that the element annotated with this annotation should be false.
@AssertTrue This annotation validate that the element annotated with this annotation should be true.
@DecimalMaxThe element annotated with this annotation should be a number which has value lower or equal to the specified number.
@DecimalMin The element annotated with this annotation should be a number which has value higher or equal to the specified number.
@FutureThe element annotated with this annotation should be an instant, date or time in the future.
@MaxThis annotation validates that the number must be lower or equal to the specified maximum.
@Min This annotation validates that the number must be a number whose value must be higher or equal to the specified minimum.
@NegativeThis annotation validates that the element must be a strictly negative number.
@NotBlankThis annotation validates that the element must not be null and should contain at least one non-whitespace character.
@NotEmptyThe element should not be null nor empty.
@NotNullThe element should not be null.
@NullThe element must be null.
@PatternThe annotated element CharSequence must match the specified regular expression.
@PositiveThe element must be a strictly positive number.
@SizeThe annotated element size should be between the specified boundaries (included).

Creating REST APIs and Model Classes

Let’s create a Rest API for Student Management with Http Get and Post methods.

StudentRestController.java :

@PostMapping(value = "/students")
public ResponseEntity<StudentVO> addStudent (@RequestBody StudentVO student)
{
    StudentDB.addStudent(student);
    return new ResponseEntity<StudentVO>(student, HttpStatus.OK);
}
 

@GetMapping(value = "/students/{id}") 
public ResponseEntity<StudentVO> getStudentById (@PathVariable("id") int id)
{
    StudentVO student = StudentDB.getStudentById(id);
     
    if(student == null) {
         throw new RecordNotFoundException("Invalid Student Id : " + id);
    }
    return new ResponseEntity<StudentVO>(student, HttpStatus.OK);
}

Now Let’s create our simple Model class or sometimes known as POJO.

Below is our StudentVO.java :

@XmlRootElement(name = "student")
@XmlAccessorType(XmlAccessType.FIELD)
public class StudentVO extends ResourceSupport implements Serializable
{
    private Integer id;
    private String firstName;
    private String lastName;
    private String email;
 
    public StudentVO(Integer id, String firstName, String lastName, String email) {
        super();
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }
 
    public StudentVO() {
    }
     
    //Setters and Getters are omitted
}

Spring Boot Exception Handling – REST Request Validation

Now Let’s move to Exception Handling of the Rest APIs –

Default Spring Validation Support

Spring does provide default validation support to its application. We are just required to add some annotation at proper places to apply the Spring default validations.

Below are the steps to apply the Spring default validations :

  • Annotation to be added in the model class.
  • Enable Validation of Request Body.

1) Annotations to be added in the model class

The Model class needs to be annotated with validation specific annotations wherever required such as @NotEmpty , @Email etc.

StudentVO.java :

@XmlRootElement(name = "student")
@XmlAccessorType(XmlAccessType.FIELD)
public class StudentVO extends ResourceSupport implements Serializable
{

    private static final long serialVersionUID = 1L;

    private Integer id;

    @NotEmpty(message = "first name cannot be empty")
    private String firstName;

    @NotEmpty(message = "last name cannot be empty")
    private String lastName;
    
    @NotEmpty(message = "email cannot be empty")
    @Email(message = "invalid email")
    private String email;
 
    public StudentVO(Integer id, String firstName, String lastName, String email) {
        super();
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }
 
    public StudentVO() {
    }
     
    //Setters and Getters are omitted
}

2) Enable Validation of Request Body.

We need to add the @Valid annotation in the Rest API Controller class in order to enable the Spring default validation for request.

StudentRestController.java :

@PostMapping(value = "/students")
public ResponseEntity<StudentVO> addStudent (@Valid @RequestBody StudentVO student)
{
    StudentDB.addStudent(student);
    return new ResponseEntity<StudentVO>(student, HttpStatus.OK);
}

Exception Model Classes

Spring Default validation support definitely works easily and it provides informations regarding the error but it provides information overload. So if we are working in large enterprise application, we should customize it according to our application’s requirement.

No Information overload is required. We need to provide the information is very few but clear words that will provide required error information. Additional information should also not be there.

We also should create the meaningful well written exceptions only and which shall describe the error in short but enough words.

We can do it either by creating separate classes for denoting specific business failures and return them whenever that specific use case fails.

For Example, Below We have created one custom RecordNotFoundException class for the scenario – where resource is not found in the system and the resource is requested by its ID.

RecordNotFoundException.java :

package com.example.exception;
 
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException 
{
    public RecordNotFoundException(String exception) {
        super(exception);
    }
}

Additionally , we have also created one more class to be returned for all failure cases.

ErrorResponse.java :

import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement(name = "error")
public class ErrorResponse 
{
    public ErrorResponse(String message, List<String> details) {
        super();
        this.message = message;
        this.details = details;
    }
 
    //General message about error
    private String message;
 
    //errors in API request processing
    private List<String> details;
 
    //Setters and Getters are omitted
}

Custom ExceptionHandler

Now Finally, we are creating a custom ExceptionHandler.

We will create one class which will extend the ResponseEntityExceptionHandler and we will annotate this class with the annotation @ControllerAdvice.

ResponseEntityExceptionHandler is a class which is a convenient base class for providing the centralized handling around all the methods of @RequestMapping. It provides centralized exception handling mechanism through the @ExceptionHandler methods.

@ControllerAdvice annotation is basically for enabling the configuration and auto scanning at the application startup.

Example : Java program for @ControllerAdvice exception handling :

package com.example.exception;

import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@SuppressWarnings({ "unchecked", "rawtypes" })
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Server Error", details);
		return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(RecordNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Record Not Found", details);
		return new ResponseEntity(error, HttpStatus.NOT_FOUND);
	}

	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		List<String> details = new ArrayList<>();
		for (ObjectError error : ex.getBindingResult().getAllErrors()) {
			details.add(error.getDefaultMessage());
		}
		ErrorResponse error = new ErrorResponse("Validation Failed", details);
		return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
	}
}

Above defined class is handling multiple exceptions such as RecordNotFoundException and also validation errors in request body etc.

Now we are all done, Let’s test the Spring Boot Exception Handling example.

Spring Boot Exception Handling – Demo

Let’s test it for different scenarios :

1) HTTP GET /students/1 – Valid

HTTP Status : 200
 
{
    "id": 1,
    "firstName": "Tech",
    "lastName": "BlogStation",
    "email": "[email protected]",
}

2) HTTP GET /students/33 – Invalid

HTTP Status : 404
 
{
    "message": "Record Not Found",
    "details": [
        "Invalid Student Id : 33"
    ]
}

3) HTTP POST /students – Invalid

****Request****

{
    "lastName": "Station",
    "email": "[email protected]"
}
****Response****

HTTP Status : 400
 
{
    "message": "Validation Failed",
    "details": [
        "first name cannot be empty"
    ]
}

4) HTTP POST /students – Invalid

****Request****

{
    "email": "[email protected]"
}
****Response****

HTTP Status : 400
 
{
    "message": "Validation Failed",
    "details": [
        "last name cannot be empty",
        "first name cannot be empty"
    ]
}

5) HTTP POST /students – Invalid

****Request****

{
    "firstName": "TechBlog",
    "lastName": "Station",
    "email": "tbs_gmail"
}
****Response****

HTTP Status : 400
 
{
    "message": "Validation Failed",
    "details": [
        "invalid email"
    ]
}

Conclusion

In this article, we have learnt how can we handle exceptions in Rest APIs in Spring Boot with examples. We also learnt to use Spring Default validations and also to customize these default behavior according to our application’s needs.

Leave a Reply

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