Exception Handling
Introduction
Good exception handling ensures your API always returns consistent and informative error responses. Spring Boot provides a centralized exception-handling mechanism.
@ExceptionHandler
Used inside a controller to handle specific exceptions.
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
@RestControllerAdvice
Handles exceptions globally across all controllers. Place it in a separate class.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(404, ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining(", "));
ErrorResponse error = new ErrorResponse(400, message);
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
ErrorResponse error = new ErrorResponse(500, "An internal server error occurred");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
Custom Exception Classes
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
Consistent Error Responses
public class ErrorResponse {
private int status;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
this.timestamp = LocalDateTime.now();
}
// getters and setters
}
Example response:
{
"status": 404,
"message": "User with id 99 not found",
"timestamp": "2026-07-16T10:30:00"
}
@ResponseStatus
Sets the HTTP status code directly on the exception class, without needing ResponseEntity.
@ResponseStatus(HttpStatus.CONFLICT) // 409
public class DuplicateEntryException extends RuntimeException {
public DuplicateEntryException(String message) {
super(message);
}
}
Exception Handling Order
| Priority | Mechanism | Use Case |
|---|---|---|
| 1 | @ExceptionHandler in the controller |
Controller-specific errors |
| 2 | @RestControllerAdvice |
Global errors |
| 3 | Spring default | Unhandled errors |
Summary
- Use
@RestControllerAdvicefor global handling. - Create custom exceptions for domain-specific errors.
- Always return a consistent error response.
Next chapter: We'll cover Spring Data JPA — how Spring Boot simplifies database access using JPA and Hibernate.