skip to content

HandlerExceptionResolver Chain

A chain of HandlerExceptionResolvers converts a thrown exception into a response, covering @ExceptionHandler methods, @ResponseStatus annotations and Spring's own defaults. Understanding the chain and its order is how you explain why your handler was skipped.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

When a Spring MVC controller throws an exception, how does the framework turn it into an HTTP response?

level: juniorimportance: must knowfreq 45%

answer

  1. DispatcherServlet.processHandlerException
  2. loop resolvers until non-null ModelAndView
  3. empty ModelAndView = handled, no view
  4. null everywhere → rethrow → /error
  5. 3 defaults in fixed order

basics

~20 s

The DispatcherServlet catches the exception and offers it to a list of HandlerExceptionResolvers. The first one that can handle it writes the response (status and body). If none handle it, the exception propagates to the servlet container.

solid answer

~30 s

When a handler (or interceptor) throws, DispatcherServlet does not let the exception escape immediately. It calls processHandlerException, which loops over its ordered list of HandlerExceptionResolver beans, invoking resolveException(request, response, handler, ex) on each. The first resolver that returns a non-null ModelAndView wins and the loop stops; an empty ModelAndView means 'handled, response already written.' If every resolver returns null, the original exception is rethrown and bubbles up to the servlet container, which in Spring Boot triggers an ERROR dispatch to /error (BasicErrorController). By default Spring registers three resolvers: ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, and DefaultHandlerExceptionResolver, tried in that order.

code

java · 23 lines
java
// The strategy DispatcherServlet iterates over:
public interface HandlerExceptionResolver {
    // null  -> not my job, try next resolver
    // ModelAndView (possibly empty) -> handled
    ModelAndView resolveException(HttpServletRequest request,
                                  HttpServletResponse response,
                                  Object handler,
                                  Exception ex);
}

// A minimal custom resolver:
public class TeapotResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest req,
                                         HttpServletResponse res,
                                         Object handler, Exception ex) {
        if (ex instanceof CoffeeRequestedException) {
            res.setStatus(418); // I'm a teapot
            return new ModelAndView(); // empty = response handled
        }
        return null; // let the next resolver try
    }
}

go deeper

for a junior

Know that DispatcherServlet delegates to a list of HandlerExceptionResolvers and the first that handles it wins.

for a middle

Name the three default resolvers and the null-vs-ModelAndView contract.

for a senior

Explain the fall-through to /error and the boundary (filters/committed responses are outside the chain).

for a principal

Discuss how this SPI unifies every error mechanism and how to extend it without breaking defaults.

## The problem A `@Controller` method can throw any exception. Something has to convert that thrown `Throwable` into a proper HTTP response (a status code, headers, and optionally a body). In Spring MVC that job belongs to the **HandlerExceptionResolver** strategy. ## HandlerExceptionResolver — the SPI It is a single-method interface: ```java public interface HandlerExceptionResolver { ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); } ``` - Return **null** = "I don't handle this exception, try the next resolver." - Return a **non-null ModelAndView** = "handled." An *empty* ModelAndView (`mav.isEmpty()` / `mav.clearEmptyRequest`) signals the response has already been fully written (e.g., status set directly), so no view needs rendering. ## Where it plugs in — DispatcherServlet `DispatcherServlet.doDispatch()` wraps handler mapping and handler execution in a try/catch. A caught `Exception` is stored, and `processDispatchResult(...)` calls `processHandlerException(request, response, handler, ex)`. That method iterates `this.handlerExceptionResolvers` (a `List<HandlerExceptionResolver>`), calling `resolveException` on each **in order** until one returns non-null. That first non-null result short-circuits the loop. ## The default chain When you use `@EnableWebMvc` or Spring Boot's autoconfiguration, `WebMvcConfigurationSupport` registers three resolvers, tried in this order: 1. **ExceptionHandlerExceptionResolver** — dispatches to your `@ExceptionHandler` methods (in controllers or `@ControllerAdvice`). 2. **ResponseStatusExceptionResolver** — handles exceptions carrying `@ResponseStatus` and `ResponseStatusException`. 3. **DefaultHandlerExceptionResolver** — maps Spring MVC's own built-in exceptions (e.g., 405, 415, 400) to status codes. ## What if nobody handles it? If all three return null, `processHandlerException` **rethrows** the exception. It leaves DispatcherServlet, goes to any Filters, then to the servlet container. In Spring Boot the container performs an internal ERROR dispatch to `/error`, served by `BasicErrorController`, producing the default JSON/Whitelabel error page. ## Key gotchas - The chain only sees exceptions thrown **inside** DispatcherServlet's dispatch (handler mapping, interceptors, handler execution). Exceptions from **Filters** run *before* DispatcherServlet and are never seen by these resolvers. - Once the response is **committed** (bytes flushed), a resolver can no longer change status — you may get a truncated/garbled response. ## When to care Every Spring MVC error-handling feature — `@ExceptionHandler`, `@ResponseStatus`, `ResponseStatusException`, the automatic 405/415 responses — is just a resolver in this chain. Understanding the chain explains *why* one mechanism wins over another.

  • What is the difference between a resolver returning null and returning an empty ModelAndView?
    null means 'I did not handle this — try the next resolver.' An empty ModelAndView means 'handled; I already wrote the response (e.g., set the status), so DispatcherServlet should not render any view.' A populated ModelAndView means 'handled — render this view/model.'
  • If no resolver handles the exception, where does it go?
    It is rethrown out of DispatcherServlet, passes back through the filter chain, and reaches the servlet container, which triggers an ERROR dispatch. In Spring Boot that lands on BasicErrorController at /error.

saying these in an interview costs you the question

  • Thinking the exception is caught by a try/catch in the controller framework and swallowed silently
  • Believing @ExceptionHandler is a special language feature rather than one resolver in a chain
  • Assuming exceptions thrown in a Servlet Filter are handled by these resolvers

context

open as a page

What are the three default HandlerExceptionResolvers Spring MVC registers, and what does each one handle?

level: middleimportance: must knowfreq 40%

basics

~10 s

ExceptionHandlerExceptionResolver (invokes @ExceptionHandler methods), ResponseStatusExceptionResolver (handles @ResponseStatus-annotated exceptions and ResponseStatusException), and DefaultHandlerExceptionResolver (maps Spring MVC's own exceptions like 405/415/400 to status codes). They run in that order.

open as a page

Explain what DefaultHandlerExceptionResolver and ResponseStatusExceptionResolver do, including the body/status subtleties.

level: seniorimportance: should knowfreq 26%

basics

~20 s

DefaultHandlerExceptionResolver maps Spring MVC's built-in exceptions (like 405, 415, 400) to status codes. ResponseStatusExceptionResolver applies the status from @ResponseStatus-annotated exceptions or a ResponseStatusException. Both mainly set the status; they don't produce a custom response body.

open as a page

How is the order of HandlerExceptionResolvers determined, and what happens if you register your own resolver?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Resolvers are sorted by their Ordered value and tried lowest-order-first; the first non-null result wins. The three defaults are ExceptionHandlerExceptionResolver (0), ResponseStatusExceptionResolver (1), DefaultHandlerExceptionResolver (lowest precedence). Use extendHandlerExceptionResolvers to add yours while keeping the defaults.

open as a page

What exceptions can the HandlerExceptionResolver chain NOT handle, and what are the key edge cases (filters, committed responses, view rendering, async)?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

The chain only handles exceptions thrown inside DispatcherServlet's dispatch (handler mapping, interceptors, handler execution). It cannot handle exceptions from Servlet Filters (they run before DispatcherServlet), from view rendering after the response is committed, or after the response is already flushed. Those fall to the container's error page.

open as a page