Global Exception Handler
In REST APIs, handling errors consistently and providing meaningful feedback to clients is a key part of creating a good developer experience.
In this lesson, we'll implement a global exception handler to manage errors across our application.
Custom Error Response Format
To maintain consistency, we'll create a simple DTO class for our error responses:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorDto {
private String error;
}Understanding Exception Handlers
Spring's exception handling mechanism allows us to centralize our error handling logic in one place. This means we can define how different types of exceptions should be handled and what response the client should receive.
Here's how we'll create our global exception handler:
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDto> handleException(Exception ex) {
log.error("Caught exception", ex);
ErrorDto errorDto = new ErrorDto();
errorDto.setError("An unknown error occurred");
return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);
}
}Handling Specific Exceptions
For validation errors, we need to handle two specific types of exceptions: ConstraintViolationException and MethodArgumentNotValidException. These occur when request validation fails:
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorDto> handleConstraintViolation(ConstraintViolationException ex) {
log.error("Caught ConstraintViolationException", ex);
String errorMessage = ex.getConstraintViolations()
.stream()
.findFirst()
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
.orElse("A constraint violation occurred");
ErrorDto errorDto = new ErrorDto();
errorDto.setError(errorMessage);
return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
} @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDto> handleMethodArgumentNotValidException(
MethodArgumentNotValidException ex
) {
log.error("Caught MethodArgumentNotValidException", ex);
ErrorDto errorDto = new ErrorDto();
BindingResult bindingResult = ex.getBindingResult();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
String errorMessage = fieldErrors.stream()
.findFirst()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.orElse("Validation error occurred");
errorDto.setError(errorMessage);
return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
}@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorDto> handleUserNotFoundException(UserNotFoundException ex) {
log.error("Caught UserNotFoundException", ex);
ErrorDto errorDto = new ErrorDto();
errorDto.setError("User not found");
return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
}Summary
- Created the
ErrorDtoclass - Created the
GlobalExceptionHandlerclass - All errors and now returned in the expected format