Course topics

By WebNest Studio Engineering Team

Spring Framework Tutorial

Exception Handling in Spring MVC

Web applications need a consistent way to turn unexpected errors and business exceptions into meaningful HTTP responses, instead of leaking raw stack traces to clients or handling errors with repetitive try-catch blocks in every controller method. Spring MVC provides @ExceptionHandler and @ControllerAdvice specifically for this purpose.

@ExceptionHandler lets a method declare which exception type it handles, and Spring routes any matching exception thrown during request processing to that method instead of letting it propagate as an unhandled 500 error. @ControllerAdvice takes this a step further by letting those handler methods apply globally, across every controller in the application, instead of being duplicated in each one.

@ExceptionHandler on a Single Controller

A method annotated with @ExceptionHandler(SomeException.class), placed inside a controller class, intercepts that exception type whenever it's thrown by any handler method in that same controller. The exception method can accept the exception instance as a parameter, and its return value is processed exactly like a normal handler's return value — a String view name for @Controller, or serialized response data for @RestController (typically paired with @ResponseStatus or wrapped in a ResponseEntity to control the HTTP status code returned).

@ControllerAdvice for Global Exception Handling

A class annotated with @ControllerAdvice (or @RestControllerAdvice, which adds @ResponseBody) centralizes exception handling across every controller in the application. Its @ExceptionHandler methods act as a global fallback: if a specific controller doesn't handle a given exception itself, Spring checks matching @ExceptionHandler methods in any @ControllerAdvice class. This keeps error-handling logic in one place instead of duplicating it across every controller.

Controlling the HTTP Response

Returning a ResponseEntity<T> from an exception handler method gives full control over both the status code and body of the error response. Alternatively, @ResponseStatus on the handler method (or directly on a custom exception class) declares a fixed HTTP status Spring should use whenever that exception is handled, which is simpler when the mapping between exception type and status code never varies.

  • @ExceptionHandler — declares which exception type a method handles.
  • @ControllerAdvice / @RestControllerAdvice — applies exception handlers globally across all controllers.
  • ResponseEntity — lets a handler set the exact status code and body of the error response.
  • @ResponseStatus — declares a fixed status code for an exception type or handler method.

Example

A global exception handler mapping a custom exception to HTTP 404

Java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Map;

class OrderNotFoundException extends RuntimeException {
    OrderNotFoundException(String id) {
        super("Order not found: " + id);
    }
}

@RestController
class OrderController {
    @GetMapping("/orders/{id}")
    public String getOrder(@PathVariable String id) {
        if (!id.equals("ORD-1")) {
            throw new OrderNotFoundException(id);
        }
        return "Order ORD-1 details";
    }
}

@RestControllerAdvice
class GlobalExceptionHandler {

    @ExceptionHandler(OrderNotFoundException.class)
    public ResponseEntity<Map<String, String>> handleNotFound(OrderNotFoundException ex) {
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(Map.of("error", ex.getMessage()));
    }
}
// GET /orders/ORD-99
Output
HTTP 404 Not Found
Body: {"error": "Order not found: ORD-99"}

Common Mistakes

  • Defining an @ExceptionHandler for a specific exception type inside one controller and expecting it to also catch that exception thrown in other controllers — local handlers only apply within their own class unless moved to a @ControllerAdvice.
  • Returning a plain object (not wrapped in ResponseEntity) from an exception handler and forgetting the response defaults to HTTP 200, even though logically it represents an error.
  • Catching an overly broad exception type like Exception.class in @ExceptionHandler, accidentally swallowing unrelated errors and masking real bugs.
  • Forgetting @RestControllerAdvice vs @ControllerAdvice — using plain @ControllerAdvice with a REST API means the handler methods need an explicit @ResponseBody or they will be treated as view names.

Exercise

Using the lesson's A global exception handler mapping a custom exception to HTTP 404 example, create a small implementation or design sketch that demonstrates @ExceptionHandler methods intercept specific exception types thrown during request handling, replacing a generic 500 error.. Add a test case or scenario that would catch Defining an @ExceptionHandler for a specific exception type inside one controller and expecting it to also catch that exception thrown in other controllers — local handlers only apply within their own class unless moved to a @ControllerAdvice., and describe the expected result.

Show hints
  • Start with @ExceptionHandler on a Single Controller and compare your approach with A global exception handler mapping a custom exception to HTTP 404. Identify the decision or behavior the lesson recommends.
  • Walk through your scenario step by step. Confirm that it follows @ExceptionHandler methods intercept specific exception types thrown during request handling, replacing a generic 500 error. and that it would catch the failure described by Defining an @ExceptionHandler for a specific exception type inside one controller and expecting it to also catch that exception thrown in other controllers — local handlers only apply within their own class unless moved to a @ControllerAdvice..

Quick quiz

1. Which choice is a mistake to avoid in Exception Handling in Spring MVC?

  • A. @ExceptionHandler methods intercept specific exception types thrown during request handling, replacing a generic 500 error.
  • B. Defining an @ExceptionHandler for a specific exception type inside one controller and expecting it to also catch that exception thrown in other controllers — local handlers only apply within their own class unless moved to a @ControllerAdvice.
  • C. Returning a plain object (not wrapped in ResponseEntity) from an exception handler and forgetting the response defaults to HTTP 200, even though logically it represents an error.
Show answer

B. Avoid this approach: Defining an @ExceptionHandler for a specific exception type inside one controller and expecting it to also catch that exception thrown in other controllers — local handlers only apply within their own class unless moved to a @ControllerAdvice. The lesson recommends: @ExceptionHandler methods intercept specific exception types thrown during request handling, replacing a generic 500 error.

Interview questions

How would you apply the guidance in @ExceptionHandler on a Single Controller to a real task, and what failure would you guard against?

Show answer

@ExceptionHandler methods intercept specific exception types thrown during request handling, replacing a generic 500 error. Apply this in @ExceptionHandler on a Single Controller; the example A global exception handler mapping a custom exception to HTTP 404 shows one way to do it. Avoid Defining an @ExceptionHandler for a specific exception type inside one controller and expecting it to also catch that exception thrown in other controllers — local handlers only apply within their own class unless moved to a @ControllerAdvice. and explain how your implementation or design prevents that failure.

Key Points to Remember

  • @ExceptionHandler methods intercept specific exception types thrown during request handling, replacing a generic 500 error.
  • @ControllerAdvice (or @RestControllerAdvice) centralizes exception handling across all controllers instead of duplicating it per class.
  • ResponseEntity gives full control over the error response's status code and body; @ResponseStatus is simpler for fixed mappings.
  • A local @ExceptionHandler in a controller takes precedence over a global one in @ControllerAdvice for that same controller.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering