Spring MVC Exception Handling: @ExceptionHandler, @ControllerAdvice

Illustration for Spring MVC Exception Handling: @ExceptionHandler, @ControllerAdvice
By Last updated:

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).

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 in UserController 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:

  1. Looking for a method annotated with @ExceptionHandler that matches the thrown exception.
  2. If not found locally, it checks global handlers (@ControllerAdvice).
  3. Spring applies the most specific exception match (e.g., IllegalArgumentException before RuntimeException).

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.