Spring - exception handling
Reading material
- https://www.baeldung.com/exception-handling-for-rest-with-spring
- 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:
- 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.
- It can return a ProblemDetail object. Spring will set the Content-Type header automatically to “application/problem+json“.
- 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
- https://www.bezkoder.com/spring-boot-controlleradvice-exceptionhandler/
- 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
- spring-aspect-oriented-programming-demo
- spring-data-jpa-file-io-with-mysql
Reading material
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
- https://www.javaguides.net/2019/08/spring-boot-crud-rest-api-spring-data-jpa-h2-database-example.html
- https://bezkoder.com/spring-boot-controlleradvice-exceptionhandler/
- https://bezkoder.com/spring-boot-restcontrolleradvice/
- https://www.baeldung.com/exception-handling-for-rest-with-spring
- https://medium.com/@jovannypcg/understanding-springs-controlleradvice-cd96a364033f