Global Exception Handling With Spring Boot


In a Spring Boot controller, things can sometimes go wrong. For example, an API consumer might not send the expected path parameter or header value. They might call the API with an invalid payload or encounter an unexpected runtime error. When these kinds of errors occur, we can use Spring Boot's Global Exception Handling mechanism to manage them and provide meaningful responses back to the API consumer.

@ExceptionHandler is used to handle exceptions in specific controllers classes and/or methods level. In a Spring Boot Restful service, errors can be handled in three different ways:

  1. Using ControllerAdvice - This annotation allows global(central) exception handling across multiple controllers.
  2. Using @ExceptionHandler at each controller level - For more granular control
  3. Returning ResponseEntity with the appropriate status code

In this post, I will explain how to implement Global Exception Handling in a Spring Boot RESTful service. 

To engage Spring Boot's Global Exception Handling (@ControllerAdvice or @RestControllerAdvice), you first need to create a class annotated with @ControllerAdvice that will handle exceptions. If you have multiple controllers or HTTP method mappings, this @ControllerAdvice class will be shared across all operations. @RestControllerAdvice is a specialised version of ControllerAdvice annotation to handling methods which are return JSON responses and which is ideal for restful APIs.

I will continue to use the controller example provided in the Spring Boot Key Annotations post. With the @RestControllerAdvice class-level annotation, we need to capture each exception with a method defined with the @ExceptionHandler annotation.

package com.chandana.helloworld.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

/**
 * @author Chandana Napagoda
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
        ErrorResponse errorResponse = new ErrorResponse("Invalid input", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<ErrorResponse> handleMissingParams(MissingServletRequestParameterException ex) {
        ErrorResponse errorResponse = new ErrorResponse("Missing parameter", ex.getParameterName());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(MissingRequestHeaderException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<ErrorResponse> handleMissingHeaders(MissingRequestHeaderException ex) {
        ErrorResponse errorResponse = new ErrorResponse("Missing header", ex.getHeaderName());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(NoResourceFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ErrorResponse> handleInvalidControllers(NoResourceFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse("Resource not found", ex.getResourcePath());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<ErrorResponse> handleAllOtherExceptions(Exception ex) {
        ErrorResponse errorResponse = new ErrorResponse("An error occurred", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

@RestControllerAdvice - A class level annotation used to define a global exception handler for entire Spring Boot application. Also it's converts handler response into JSON format.

@ControllerAdvice - A class level annotation used to define a global exception handler for entire Spring Boot application.

@ExceptionHandler - This is a method level annotation used within the @ControllerAdvice class to specify the type of exception that method handles. 

Below are a few API responses before and after introducing Controller Advice.



Bad Request Response before and after Global Exception Handler

Bad Request Error

Resource Not Found with and without ControllerAdvice
Resource Not Found


In addition to your own custom exceptions, Java standard exceptions, database exceptions, security exceptions, and file handling exceptions, these are all the known exception implementations exposed by the Spring Framework.

As a conclusion, by implementing @ControllerAdvice/@RestControllerAdvice in our Spring Boot application, we can easily manage and centralise exception handling across all controllers. Overall this will improve code maintainability and provide better error handling in API level. 

Please refrain from copying content to other websites. You can download the Swagger Spring Boot Global Exception Handling Project source code from my GitHub repository.

No comments:

Post a Comment


View Counts

Followers