LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2519 min read

Exception Handling in Spring MVC

Learn how @ExceptionHandler and @ControllerAdvice centralize error handling across a Spring MVC application.

Introduction

Every application eventually deals with things going wrong - a requested record does not exist, a downstream service times out, or invalid input slips past validation. Spring MVC gives you a clean way to handle these situations without wrapping every controller method in try/catch: @ExceptionHandler methods, optionally centralized across the whole application with @ControllerAdvice. This lesson shows both.

What You Will Learn
  • Why scattering try/catch across controllers does not scale.
  • How @ExceptionHandler catches exceptions thrown from handler methods.
  • How @ControllerAdvice centralizes exception handling application-wide.
  • How to return structured, consistent error responses.

The Problem with Scattered try/catch

Without a dedicated mechanism, handling errors means wrapping the body of every controller method in try/catch, duplicating the same error-formatting logic dozens of times, and inevitably missing some cases. That duplication makes error responses inconsistent and the code harder to maintain.

// Repetitive, error-prone approach
@GetMapping("/books/{id}")
public ResponseEntity<Book> getBook(@PathVariable Long id) {
try {
return ResponseEntity.ok(bookService.findById(id));
} catch (BookNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}

@ExceptionHandler in a Controller

A method annotated with @ExceptionHandler inside a controller catches exceptions of the specified type thrown by any handler method in that same controller, letting you remove the try/catch entirely.

@Controller
@RequestMapping("/books")
public class BookController {
@GetMapping("/{id}")
public String bookDetails(@PathVariable Long id, Model model) {
model.addAttribute("book", bookService.findById(id)); // throws if missing
return "book-details";
}
@ExceptionHandler(BookNotFoundException.class)
public String handleNotFound(BookNotFoundException ex, Model model) {
model.addAttribute("message", ex.getMessage());
return "error-404";
}
}

bookService.findById(id) now simply throws BookNotFoundException when nothing is found, and handleNotFound() takes over automatically - no try/catch needed in bookDetails() itself.

Centralizing with @ControllerAdvice

An @ExceptionHandler method scoped to one controller only helps that controller. @ControllerAdvice lets you define exception handling once, in a dedicated class, and have it apply across every controller in the application.

@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public String handleNotFound(BookNotFoundException ex, Model model) {
model.addAttribute("message", ex.getMessage());
return "error-404";
}
@ExceptionHandler(Exception.class)
public String handleGeneric(Exception ex, Model model) {
model.addAttribute("message", "Something went wrong. Please try again.");
return "error-generic";
}
}
Order Matters

Spring matches the most specific exception type first, so a handler for BookNotFoundException runs instead of the generic Exception handler when a BookNotFoundException is thrown, even though both are defined in the same class.

Returning Structured Error Responses

For a REST API, an @ExceptionHandler typically returns a structured error body plus the correct HTTP status, using ResponseEntity or @ResponseStatus, instead of a view name.

@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<Map<String, String>> handleNotFound(BookNotFoundException ex) {
Map<String, String> body = Map.of("error", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
}
}
Example JSON Response (404)

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Catching Exception too broadly at the top and swallowing errors that should surface real bugs during development.
  • Duplicating the same @ExceptionHandler logic in multiple controllers instead of centralizing it with @ControllerAdvice.
  • Returning HTTP 200 for error cases because the response body happens to render successfully.
  • Forgetting @RestControllerAdvice (or @ResponseBody) when building a JSON API, resulting in the error handler trying to resolve a view name instead.
  • Leaking internal exception details (stack traces, SQL text) directly into a user-facing error response.

Best Practices

  • Centralize exception handling with @ControllerAdvice (or @RestControllerAdvice for APIs) rather than repeating it per controller.
  • Create specific, meaningful exception classes (BookNotFoundException) instead of throwing generic RuntimeException everywhere.
  • Always set the correct HTTP status code for the error being reported.
  • Keep a broad fallback handler for Exception.class as a safety net, but make it generic and never leak internals.
  • Log the full exception server-side even when the response to the client is simplified.

Frequently Asked Questions

@RestControllerAdvice is @ControllerAdvice combined with @ResponseBody on every handler method, so returned objects are written directly to the response body as JSON, matching @RestController.

Yes - different handler methods within the same class can return a view name (String), a ResponseEntity, or a plain object, depending on what each specific error case needs.

No - it only catches exceptions thrown during the execution of controller/handler code that DispatcherServlet invokes. Exceptions thrown earlier in the filter chain need separate handling.

Key Takeaways

  • @ExceptionHandler methods catch specific exception types thrown by handler methods, removing scattered try/catch.
  • @ControllerAdvice centralizes exception handling across every controller in the application.
  • @RestControllerAdvice combines @ControllerAdvice with @ResponseBody for JSON APIs.
  • Spring matches the most specific applicable exception handler first.
  • Error responses should use the correct HTTP status and never leak internal details.

Summary

Centralized exception handling keeps controllers focused on the happy path while still producing consistent, well-formed error responses. Having covered the request-handling side of Spring MVC, the course now turns to the data layer - starting with why Spring JDBC exists and how it cuts down raw JDBC boilerplate.

Next Lesson →

Spring JDBC Basics