Spring - exception handling

Reading material

  1. https://www.baeldung.com/exception-handling-for-rest-with-spring
  2. https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

Overview

We really need a consistent way of dealing with exceptions. Spring Boot provides ways of doing that:

Controller Based (local) Exception Handling

We can use @ExceptionHandler to annotate methods that Spring automatically invokes when the given exception occurs. We can specify the exception either with the annotation or by declaring it as a method parameter, which allows us to read out details from the exception object to handle it correctly. The method itself is handled as a Controller method, so:

  1. It can return an object that is rendered into the response body, or a complete ResponseEntity. Content Negotiation is allowed here since Spring 6.2.
  2. It can return a ProblemDetail object. Spring will set the Content-Type header automatically to “application/problem+json“.
  3. We can specify a return code with @ResponseStatus.

The simplest exception handler that returns a 400 status code could be:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(CustomException1.class)
public void handleException1() { }
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({
    CustomException4.class,
    CustomException5.class
})
public ResponseEntity<CustomExceptionObject> handleException45(Exception ex) {
    // ...
}

We can place handler methods in the controller class:

@RestController
public class FooController {
    //...

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(CustomException1.class)
    public void handleException() {
        // ...
    }
}

Global Exception Handling using ControllerAdvice

  1. https://www.bezkoder.com/spring-boot-controlleradvice-exceptionhandler/
  2. https://www.baeldung.com/exception-handling-for-rest-with-spring

A @ControllerAdvice contains code that is shared between multiple controllers.

Here is an example to do exception type conversion before sending the errors to client applications.

Using ControllerAdvice for exception handling:

import com.mycompany.model.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
@Slf4j
public class ApplicationErrorHandler {

  @ExceptionHandler(ValidationException.class)
  public ResponseEntity<ErrorResponse> handleValidationErrors(ValidationException exception) {
    ErrorResponse errorResponse =
      Optional.ofNullable(exception.getErrorResponse())
        .orElse(new ErrorResponse()
                  .status(exception.getStatus())
                  .messages(exception.getMessages())
                  .identifier(exception.getIdentifier())
      );
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
  }

  @ExceptionHandler(ProcessingException.class)
  public ResponseEntity<ErrorResponse> handleProcessingErrors(ProcessingException exception) {
    ErrorResponse errorResponse =
      Optional.ofNullable(exception.getErrorResponse())
        .orElse(new ErrorResponse()
                  .status(exception.getStatus())
                  .messages(exception.getMessages())
                  .identifier(exception.getIdentifier())
      );
    return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }

  @ExceptionHandler(UserAuthorizationException.class)
  public ResponseEntity<ErrorResponse> handleUserAuthorizationErrors(UserAuthorizationException exception) {
    ErrorResponse errorResponse =
      Optional.ofNullable(exception.getErrorResponse())
        .orElse(new ErrorResponse()
                  .status(exception.getStatus())
                  .messages(exception.getMessages())
                  .identifier(exception.getIdentifier())
      );
    return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED);
  }

  @ExceptionHandler(PendingException.class)
  public ResponseEntity<ErrorResponse> handlePendingErrors(PendingException exception) {
    ErrorResponse errorResponse =
      Optional.ofNullable(exception.getErrorResponse())
        .orElse(new ErrorResponse()
                  .status(exception.getStatus())
                  .messages(exception.getMessages())
                  .identifier(exception.getIdentifier())
      );
    return new ResponseEntity<>(errorResponse, HttpStatus.ACCEPTED);
  }

  @ExceptionHandler(ResourceNotFoundException.class)
  public ResponseEntity<ErrorResponse> handleResourceNotFoundErrors(ResourceNotFoundException exception) {
    ErrorResponse errorResponse =
      Optional.ofNullable(exception.getErrorResponse())
        .orElse(new ErrorResponse()
                  .status(exception.getStatus())
                  .messages(exception.getMessages())
                  .identifier(exception.getIdentifier())
      );
    return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleUncaughtErrors(Exception exception) {
    ErrorResponse errorResponse =
      Optional.ofNullable(exception.getErrorResponse())
        .orElse(new ErrorResponse()
                  .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
                  .messages(exception.getMessages())
                  .identifier(exception.getIdentifier())
      );
    return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}

Sample implementations

  1. spring-aspect-oriented-programming-demo
  2. spring-data-jpa-file-io-with-mysql

Reading material

  1. https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
  2. https://www.javaguides.net/2019/08/spring-boot-crud-rest-api-spring-data-jpa-h2-database-example.html
  3. https://bezkoder.com/spring-boot-controlleradvice-exceptionhandler/
  4. https://bezkoder.com/spring-boot-restcontrolleradvice/
  5. https://www.baeldung.com/exception-handling-for-rest-with-spring
  6. https://medium.com/@jovannypcg/understanding-springs-controlleradvice-cd96a364033f