Global Exception Handling in Spring Boot Applications

Global Exception Handling in Spring Boot Applications

The bigger your application gets – the harder it becomes to handle all possible exceptions in a same manner. Also what if one of the API’s has some other specific requirements? For example it needs to be compliant with some standard and all of its exception has to be standardised to it.

Good news Spring engineers already though about that. So in this post we will discuss what are the options to exceptions globally.

Case One:

General exception handling, all APIs needs to have the same exception structure. Architecture diagram:

Implementation is really straightforward in this case, we just need to create GeneralExceptionHandler class, annotate it with @ControllerAdvice annotation and create required @ExceptionHandlers which will process all exceptions thrown by the application and if it will find matching @ExceptionHandler it will transform it accordingly. Code example:

@ControllerAdvice
public class GeneralExceptionHandler {

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Error> handleException(Exception ex) {
       MyError myError = MyError.builder()
                         .text(ex.getMessage())
                         .code(ex.getErrorCode()).build();
       return new ResponseEntity(myError,
                               HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Case Two:

We have an API which needs to have one or more of its exceptions to be handled in other format from the rest of the application APIs. Architecture diagram:

There is two paths we can take implementing this case. We can add @ExceptionHandler to handle OtherException inside OtherController it self or create new @ControllerAdvice for only OtherController incase we might want to handle OtherException in some other API too.

Code example of adding @ExceptionHandler to handle OtherException inside OtherController:

@RestController
@RequestMapping("/other")
public class OtherController {

    @ExceptionHandler(OtherException.class)
    protected ResponseEntity<Error> handleException(OtherException ex) {
      MyOtherError myOtherError = MyOtherError.builder()
                         .message(ex.getMessage())
                         .origin("Other API")
                         .code(ex.getErrorCode()).build();
      return new ResponseEntity(myOtherError,
                               HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Code example of @ControllerAdvice for only OtherController:

@ControllerAdvice(assignableTypes = OtherController.class)
public class OtherExceptionHandler {

    @ExceptionHandler(OtherException.class)
    protected ResponseEntity<Error> handleException(OtherException ex) {
      MyOtherError myOtherError = MyOtherError.builder()
                         .message(ex.getMessage())
                         .origin("Other API")
                         .code(ex.getErrorCode()).build();
      return new ResponseEntity(myOtherError,
                               HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Case Three:

Similarly as in case two we have an API which needs to have exceptions formatted in a different way from other APIs in an application, but this time all of the exceptions needs to be transformed differently. Architecture diagram:

To implement this case we will have to use two @ControllerAdvices with a caveat of @Order annotation. Because now we will need to tell Spring which of our @ControllerAdvices has higher priority when handling the same exception. If we wouldn’t have @Order specified, on startup one of the handlers will register with higher order automatically and our exception handling will become unpredictable. For example I recently saw a case when if you start an app using bootRun gradle task OtherExceptionHandler was primary, but when started as jar GeneralExceptionHandler was primary. Code example:

@ControllerAdvice
public class GeneralExceptionHandler {

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Error> handleException(Exception ex) {
       MyError myError = MyError.builder()
                         .text(ex.getMessage())
                         .code(ex.getErrorCode()).build();
       return new ResponseEntity(myError,
                               HttpStatus.valueOf(ex.getErrorCode()));
    }
}

@ControllerAdvice(assignableTypes = OtherController.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class OtherExceptionHandler {

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Error> handleException(Exception ex) {
       MyError myError = MyError.builder()
                         .message(ex.getMessage())
                         .origin("Other API")
                         .code(ex.getErrorCode()).build();
       return new ResponseEntity(myError,
                               HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Conclusion

There is many other scenarios where you might need to setup global exception handling for your application and many possible ways to do that as well, but I hope this post helped you so solve some of those scenarios.

Add Comment

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