Signup/Sign In

Exception handling for REST API in Spring

Posted in Programming   LAST UPDATED: AUGUST 23, 2021

    In software development, exception handling is a way or mechanism to handle any abnormality in the code at runtime in order to maintain the normal flow of the program. The most common way to apply exception handling in our code is by using try catch blocks. Suppose we are designing a simple microservice with a controller, service and DAO class, where all the exceptions are being handled in the controller itself, whether it occurs in the service, DAO, or the controller itself.

    Exception Handling in the Controller:

    In the sample application, we are having only a controller in which we are manually throwing an exception to replicate the flow of any normal microservice. Let's start with the example:

    @RestController
    public class DemoController {
       
         @RequestMapping("exception/arithmetic")
         public String controllerForArithmeticException()
         {
               throw new ArithmeticException("Divide by zero error");
         }
    }

    Response in postman: (http://localhost:8080/exception/arithmetic)


    Status: 500 Internal Server Error
    Body:
    {
    "timestamp": "2020–02–28T17:21:50.860+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Divide by zero error",
    "path": "/exception/arithmetic"
    }

    Now let's add a try-catch block to handle the exception as we don't want that our browser receives the exception details. It is not a good practice to leave the exceptions unhandled as it reveals the details of our internal system to the browser (client).

    @RestController
    public class DemoController {
     
         @RequestMapping("exception/arithmetic")
         public String controllerForArithmeticException()
         {
              try {
                     throw new ArithmeticException("Divide by zero error");
              }
              catch(ArithmeticException ae){
                     return ae.getMessage();
              }
         }
    }

    Response in postman: (http://localhost:8080/exception/arithmetic)


    Status: 200 Ok
    Body:
    Divide by zero error

    Now suppose we are exposing two endpoints in our microservice. In this case, we need to handle exceptions in both the controller methods separately using try-catch blocks as it can be seen below:

    @RestController
    public class DemoController {
     
         @RequestMapping("exception/arithmetic")
         public String controllerForArithmeticException()
         {
              try {
                     throw new ArithmeticException("Divide by zero error");
              }
              catch(ArithmeticException ae){
                     return ae.getMessage();
              }
         }
     
         @RequestMapping("exception/arithmetic2")
         public String controllerForArithmeticException2()
         {
              try {
                     throw new ArithmeticException("Divide by zero error");
              }
              catch(ArithmeticException ae){
                     return ae.getMessage();
              } 
         }
    }

    This adds a lot of boilerplate codes. One more reason not to add exception handling code in our controller is to facilitate the separation of concerns. Since exception handling is not the part of our main business or/and it doesn't belong to the controller, it should be done in a separate class. So here comes the concept of global exception handling in spring boot in which any exception occurring in the controllers will be handled inside a separate single class globally.

    Using @ControllerAdvice and @ExceptionHandler

    The @ControllerAdvice is a Spring Boot annotation to handle exceptions globally and it was introduced in Spring 3.2. A class annotated with it consists of the Exception handlers, annotated with @ExceptionHandler handling the exceptions occurring in any controller of the application. In simple terms, it intercepts all the exceptions occurring in any of the controllers. The following strategy may be followed while adding global exception handling in our spring boot application:

    1. Create and use data class for the exception: Instead of using the built-in exception classes, we must always create our own exception data class to represent the exceptions and use it.

    2. Single @ControllerAdvice annotated class per application: A single class must be annotated with @ControllerAdvice annotation and all the exception handlers annotated with @ExceptionHandler must be added in the same instead of having multiple @ControllerAdvice annotated classes.

    3. Single @ExceptionHandler annotated handler method per exception: There should be one @ExceptionHandler annotated handler method per exception defined in the @ControllerAdvice annotated class. All the logic handling that exception must be performed there.

    Let’s understand the above theory with an example application. Create a spring boot application.

    Main class:

    It's a usual and simple main class of a spring boot application.

    @SpringBootApplication
    public class DemoApplication {
          public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
          }
    }

    Data class for the exception:

    It will be used to send the exception message to the client instead of the actual occurred exception object.

    @Getter
    @Setter
    public class ApiError {
          
          String exception;
    }

    Controller:

    A simple REST controller class with two endpoints which are intentionally throwing exceptions to replicate such case in real life.

    @RestController
    public class DemoController {
        
         @RequestMapping("exception/arithmetic")
         public String controllerForArithmeticException()
         {
               throw new ArithmeticException("Divide by zero error");
         }
        
         @RequestMapping("exception/null-pointer")
         public String controllerForException() throws Exception
         {
               throw new NullPointerException("Null reference");
         }
    }

    Controller Advice:

    The main logic of the global exception for a REST API. It has exception handlers for all the possible exceptions that can be thrown by the controller and also an exception handler for "Exception" class in order to handle any other exception that wouldn't be caught by other exception handlers.

    @ControllerAdvice
    public class GlobalExceptionHandler{
         
         @ExceptionHandler(value = ArithmeticException.class)
         public ResponseEntity<ApiError> handleArithmeticException(ArithmeticException e)
         {
               ApiError error = new ApiError();
               error.setException("Arithmetic exception: " + e.getMessage());
               HttpStatus status = HttpStatus.BAD_REQUEST;
               return new ResponseEntity<ApiError>(error, status);
         }
       
         @ExceptionHandler(value = NullPointerException.class)
         public ResponseEntity<ApiError> handleNullPointerException(NullPointerException e)
         {
               ApiError error = new ApiError();
               error.setException("Null pointer exception: " + e.getMessage());
               HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
               return new ResponseEntity<ApiError>(error, status);
         }
    
         @ExceptionHandler(value = Exception.class)
         public ResponseEntity<ApiError> handleNullPointerException(Exception e)
         {
               ApiError error = new ApiError();
               error.setException("Exception: " + e.getMessage());
               HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
               return new ResponseEntity<ApiError>(error, status);
         }
    }

    Now, @ControllerAdvice is intercepting all the exceptions occurring in the controller and is ready for global exception handling.

    Response in postman: (http://localhost:8080/exception/arithmetic)


    Status: 400 Bad Request
    Body:
    {
    "exception": "Arithmetic exception: Divide by zero error"
    }

    Conclusion:

    Using @ControllerAdvice and @ExceptionHandler annotations for global exception handling in spring boot allows us to keep all our exception handling logic in a single place and removes the boilerplate codes that exist in the case of try-catch blocks.

    You may also like:

    About the author:
    I'm a writer at studytonight.com.
    Tags:MicroservicesSpring BootException HandlingJava
    IF YOU LIKE IT, THEN SHARE IT
     

    RELATED POSTS