Spring MVC Exception Handling: @ExceptionHandler, @ControllerAdvice
Slug: spring-mvc-exception-handling-exceptionhandler-controlleradvice
Description: Learn Spring MVC exception handling with @ExceptionHandler and @ControllerAdvice. Best practices, examples, and robust API design explained.
Tags: Java exception handling, Spring MVC exceptions, @ExceptionHandler, @ControllerAdvice, REST API error handling, checked vs unchecked exceptions, custom exceptions, best practices, logging exceptions, resilient APIs
Category: Java
Series: Java-Exception-Handling
Introduction
In Spring MVC, exception handling plays a crucial role in building reliable web applications and REST APIs. Instead of exposing stack traces to users or writing repetitive try-catch
blocks in controllers, Spring provides powerful annotations like @ExceptionHandler
and @ControllerAdvice
for centralized error handling.
Think of it as the airbag system in a car: you hope you never need it, but when an error occurs, it saves your application from crashing disastrously while providing a user-friendly error response.
Core Concepts of Exception Handling in Spring MVC
Spring MVC builds exception handling on top of Java’s core Throwable
hierarchy:
Error
: Serious problems not intended to be caught (e.g.,OutOfMemoryError
).Exception
: Recoverable conditions in applications.- Checked exceptions: Must be declared with
throws
(e.g.,IOException
). - Unchecked exceptions (RuntimeException): Do not need explicit declaration (e.g.,
NullPointerException
).
- Checked exceptions: Must be declared with
In a web context, exceptions often arise from:
- Invalid input or missing parameters
- Database connectivity issues
- Security/authentication problems
- Business logic violations
Spring MVC provides tools to handle these consistently and gracefully.
@ExceptionHandler: Local Exception Handling
The @ExceptionHandler
annotation allows handling exceptions at the controller level.
Example
@Controller
public class UserController {
@GetMapping("/user/{id}")
public String getUser(@PathVariable int id) {
if (id <= 0) {
throw new IllegalArgumentException("Invalid user ID");
}
return "user";
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
return new ResponseEntity<>("Error: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
- Here, any
IllegalArgumentException
thrown inUserController
will be intercepted by the handler method. - This keeps error handling close to the controller logic.
Limitation: Duplication across multiple controllers.
@ControllerAdvice: Global Exception Handling
@ControllerAdvice
provides global exception handling across all controllers, promoting consistency and reusability.
Example
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
return new ResponseEntity<>("Invalid input: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneral(Exception ex) {
return new ResponseEntity<>("Something went wrong: " + ex.getMessage(),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Benefits:
- Centralized exception management
- Consistent error responses across controllers
- Cleaner controller code
Combining @ExceptionHandler and @ControllerAdvice
- Use
@ExceptionHandler
for controller-specific error handling. - Use
@ControllerAdvice
for cross-cutting concerns and global error strategy.
Together, they balance local flexibility and global consistency.
Exception Hierarchy and Matching in Spring
Spring determines which handler to use by:
- Looking for a method annotated with
@ExceptionHandler
that matches the thrown exception. - If not found locally, it checks global handlers (
@ControllerAdvice
). - Spring applies the most specific exception match (e.g.,
IllegalArgumentException
beforeRuntimeException
).
Best Practices
- Always log exceptions with frameworks like SLF4J or Logback.
- Design clear, meaningful error messages for API clients.
- Avoid catching
Error
(e.g.,OutOfMemoryError
). - Group related exception handling logic into reusable classes.
- Use custom exceptions for domain-specific errors.
Real-World Scenarios
REST API Error Handling
@ControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(ResourceNotFoundException ex) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
Clients now receive structured JSON error responses instead of raw HTML.
Database Access
Wrap SQLException
into a custom exception like DatabaseException
and handle it globally for clean API responses.
Security & Authentication
Return HttpStatus.UNAUTHORIZED
(401) or HttpStatus.FORBIDDEN
(403) in global exception handlers.
📌 What's New in Java Versions?
- Java 7+: Multi-catch, try-with-resources simplify cleanup logic.
- Java 8: Lambdas and streams—exceptions in functional interfaces must be carefully managed.
- Java 9+: Stack-Walking API for better debugging.
- Java 14+: Helpful
NullPointerExceptions
with detailed messages. - Java 21: Structured concurrency enhances exception propagation across virtual threads.
FAQ
Q1: Can I define multiple @ExceptionHandler methods for different exceptions in one controller?
Yes, each method can target a specific exception type.
Q2: What happens if multiple handlers match the same exception?
Spring picks the most specific exception handler.
Q3: Should I catch and rethrow exceptions in controllers?
Prefer throwing exceptions and letting @ControllerAdvice
handle them globally.
Q4: Can I customize error response format in Spring MVC?
Yes, by returning a DTO (e.g., ApiError
) as JSON/XML.
Q5: How do I log exceptions in global handlers?
Inject a logger (private static final Logger log = LoggerFactory.getLogger(...)
) and log the error.
Q6: Is @ControllerAdvice only for exceptions?
No, it can also handle @InitBinder
and @ModelAttribute
globally.
Q7: Can @RestControllerAdvice be used instead?
Yes, it’s a variant that automatically applies @ResponseBody
for JSON/XML responses.
Q8: What is the difference between checked and unchecked exceptions in Spring MVC?
Checked exceptions must be declared with throws
; unchecked don’t require declaration.
Q9: How do I handle exceptions in async requests?
Spring MVC propagates them to global handlers; you can also customize via CallableProcessingInterceptor
.
Q10: Should I expose stack traces in API responses?
No, hide internal details and provide meaningful error messages instead.
Conclusion and Key Takeaways
- Use
@ExceptionHandler
for local controller exceptions. - Use
@ControllerAdvice
for global exception handling. - Provide meaningful and consistent error responses for better user experience.
- Log exceptions properly and avoid swallowing them.
- Leverage Java’s evolving features to improve debugging and error resilience.