7.2 @RestControllerAdvice and @ExceptionHandler
Instead of handling exceptions in each individual controller, Spring provides powerful features called @RestControllerAdvice (or @ControllerAdvice) that allow you to gather and handle exceptions occurring globally throughout the application in one place.
Creating a Global Exception Handler
By declaring @RestControllerAdvice at the class level, the class becomes responsible for global exception handling. If you attach @ExceptionHandler to an internal method, that method intercepts and executes when a specific type of exception occurs.
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* Global handling for NullPointerException
*/
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException e) {
log.error("Null reference occurred on the server", e);
ErrorResponse response = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"An internal server problem occurred."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
/**
* Handle Database-related exceptions (Persistence Layer)
*/
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDataAccessException(DataAccessException e) {
log.error("Database access error", e);
ErrorResponse response = new ErrorResponse(500, "A problem occurred while connecting to the database.");
return ResponseEntity.internalServerError().body(response);
}
/**
* Handle invalid parameter type requests (e.g., sending text where a number is required)
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
String message = String.format("The type for parameter '%s' is invalid.", e.getName());
ErrorResponse response = new ErrorResponse(400, message);
return ResponseEntity.badRequest().body(response);
}
/**
* Fallback for custom or all other exceptions
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllException(Exception e) {
log.error("Unexpected exception occurred", e);
ErrorResponse response = new ErrorResponse(500, "An unknown system error occurred.");
return ResponseEntity.internalServerError().body(response);
}
}
Validation Exception Handling
When a Bean Validation error learned in Chapter 6 occurs, Spring throws a MethodArgumentNotValidException. We can globally handle this and return it in our desired format (e.g., combining the field name where the error occurred and the message).
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
// ... Inside GlobalExceptionHandler
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
StringBuilder errorMessage = new StringBuilder();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
errorMessage.append(fieldError.getField())
.append(" field ")
.append(fieldError.getDefaultMessage())
.append("; ");
}
ErrorResponse response = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
errorMessage.toString()
);
return ResponseEntity.badRequest().body(response);
}
By utilizing @RestControllerAdvice, you can cohere the exception handling logic scattered throughout the application into a single class, significantly improving maintainability and consistency.