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.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Mastering Adaptive Global Exception Handling in Spring Boot: Tips & Pitfalls

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.

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.

JavaSpring BootInterceptorErrorControllerglobal exception handling
Java Backend Technology
Written by

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!

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.