Mastering Adaptive Global Exception Handling in Spring Boot: Tips & Pitfalls
This article explains how to implement adaptive global exception handling in Spring Boot, demonstrates default behavior, walks through key code examples, highlights common pitfalls such as interceptor loops, and offers practical solutions for robust error management in backend services.
Demo
First we look at the default behavior of Spring Boot.
Browser access
Client access
Key point
Most company code lacks adaptive handling; searching for "Springboot global exception handling" often returns a generic snippet.
@ControllerAdvice
public class MyControllerAdvice {
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ResponseEntity<?> errorHandler(Exception ex) {
// handle exception
}
}It is strongly recommended to search with your usual engine first, then examine your own code to see if a similar snippet exists.
Some argue that only JSON is returned and not HTML, but for infrastructure developers adaptive handling is essential because they serve the whole company's business units.
Adaptive principle
Instead of digging into the massive Spring Boot source code, consult the official documentation.
27.1.9 Error Handling Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container. For machine clients it will produce a JSON response with details of the error, the HTTP status and the exception message. For browser clients there is a ‘whitelabel’ error view that renders the same data in HTML format (to customize it just add a View that resolves to ‘error’). To replace the default behaviour completely you can implement ErrorController and register a bean definition of that type, or simply add a bean of type ErrorAttributes to use the existing mechanism but replace the contents.
Key terms to focus on:
clients JSON browser HTML ErrorController.
Focusing on ErrorController, here is the core code from Spring Boot:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(
getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
}From this we can infer a custom adaptive global exception handler:
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleExceptionHtml(Exception e, HttpServletRequest request) {
// custom handling, e.g., set attributes
request.setAttribute("welcome", "Follow our WeChat");
return "forward:/error";
}
}Testing confirms it works; customizing the error page is straightforward.
Encountered problems
If an interceptor throws an exception, it can cause a StackOverflowError because the exception is caught by the global handler, which forwards back to the interceptor, creating a loop.
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
throw new RuntimeException("Simulated exception");
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
});
}
}Solution: exclude the error path from the interceptor or adjust configuration.
Hard‑coding configuration
registry.addInterceptor(new HandlerInterceptor() { ... })
.excludePathPatterns("/error");The error.path can be overridden, so hard‑coding /error may break when the path changes.
Implicit conventions
Many projects require hidden parameters such as excludePathPatterns, which become maintenance burdens and are hard for new team members to discover.
Further thoughts
To avoid hidden conventions, make such parameters explicit in configuration, document them, and provide utilities to enforce them. For interceptor ordering, use @Order or define a dedicated traceInterceptor that must be placed first, ensuring predictable execution order.
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
