Mastering Spring MVC Exception Handling: A Deep Dive into HandlerExceptionResolver

This article explains how Spring MVC processes exceptions using the DispatcherServlet and a configurable HandlerExceptionResolver chain, outlines the built‑in resolver implementations, shows how to customize error pages, and provides code examples for handling errors in both HTML and JSON responses.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring MVC Exception Handling: A Deep Dive into HandlerExceptionResolver

Environment

Spring 5.3.24

Overview

When an exception occurs during request mapping or is thrown from a request handler (e.g., a @Controller), the DispatcherServlet delegates the exception to a chain of HandlerExceptionResolver beans. The resolvers attempt to handle the exception and usually produce an error response.

Available HandlerExceptionResolver Implementations

SimpleMappingExceptionResolver : Maps exception class names to error view names; used to render error pages in browser applications.

DefaultHandlerExceptionResolver : Resolves exceptions raised by Spring MVC and maps them to HTTP status codes. Optional ResponseEntityExceptionHandler and REST API exception handling are also available.

ResponseStatusExceptionResolver : Uses the @ResponseStatus annotation to map an exception to an HTTP status code based on the annotation's value.

ExceptionHandlerExceptionResolver : Resolves exceptions by invoking methods annotated with @ExceptionHandler in @Controller or @ControllerAdvice classes.

Exception Resolver Chain

Multiple HandlerExceptionResolver beans can be declared in Spring configuration. Their order property determines the chain order—higher order values are processed later.

A resolver can return:

A ModelAndView that points to an error view.

An empty ModelAndView if the exception has been handled. null to let the next resolver in the chain try; if all resolvers return null, the exception bubbles up to the servlet container.

Container Error Page Configuration

If no HandlerExceptionResolver resolves the exception and the response status is an error (4xx/5xx), the servlet container renders a default error page. You can customize this page by declaring an <error-page> mapping in web.xml:

<error-page>
  <location>/error</location>
</error-page>

When the container forwards to the configured URL (e.g., /error), the DispatcherServlet can route the request to a controller that returns either an error view or a JSON response, as shown below:

@RestController
public class ErrorController {

  @RequestMapping(path = "/error")
  public Map<String, Object> handle(HttpServletRequest request) {
    Map<String, Object> map = new HashMap<>();
    map.put("status", request.getAttribute("javax.servlet.error.status_code"));
    map.put("reason", request.getAttribute("javax.servlet.error.message"));
    return map;
  }
}

Error Handling Mechanism in DispatcherServlet

public class DispatcherServlet {
  // Retrieve all exception resolvers from the container
  private List<HandlerExceptionResolver> handlerExceptionResolvers;

  protected void initStrategies(ApplicationContext context) {
    // Initialize exception resolvers
    initHandlerExceptionResolvers(context);
  }

  private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;
    if (this.detectAllHandlerExceptionResolvers) {
      Map<String, HandlerExceptionResolver> matchingBeans =
          BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
      if (!matchingBeans.isEmpty()) {
        this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
        AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
      }
    }
  }

  protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
        exMv = resolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
          break;
        }
      }
    }
    if (exMv != null) {
      return exMv;
    }
    // If all resolvers return null, rethrow the exception for the servlet container to handle
    throw ex;
  }
}

Default Resolver Configuration

public class WebMvcAutoConfiguration {
  @Configuration(proxyBeanMethods = false)
  @EnableConfigurationProperties(WebProperties.class)
  public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
  }
}

public class WebMvcConfigurationSupport {
  @Bean
  public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager")
      ContentNegotiationManager contentNegotiationManager) {
    List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
    // Configure custom resolvers if any
    configureHandlerExceptionResolvers(exceptionResolvers);
    if (exceptionResolvers.isEmpty()) {
      // Add default resolvers
      addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
    }
    extendHandlerExceptionResolvers(exceptionResolvers);
    HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
    composite.setOrder(0);
    composite.setExceptionResolvers(exceptionResolvers);
    return composite;
  }

  protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
      ContentNegotiationManager mvcContentNegotiationManager) {
    ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
    exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
    exceptionHandlerResolver.setMessageConverters(getMessageConverters());
    exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
    exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
    if (jackson2Present) {
      exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
    }
    if (this.applicationContext != null) {
      exceptionHandlerResolver.setApplicationContext(this.applicationContext);
    }
    exceptionHandlerResolver.afterPropertiesSet();
    exceptionResolvers.add(exceptionHandlerResolver);
    ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
    responseStatusResolver.setMessageSource(this.applicationContext);
    exceptionResolvers.add(responseStatusResolver);
    exceptionResolvers.add(new DefaultHandlerExceptionResolver());
  }
}

End of article.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentException HandlingSpring MVCHandlerExceptionResolver
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.