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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
