Customizing Spring Boot Error Handling for Non‑Controller Exceptions
This guide explains why Spring Boot’s default @ControllerAdvice cannot catch servlet‑level errors, introduces the ErrorPageFilter mechanism, and shows how to replace the BasicErrorController with a custom ExceptionController that rethrows errors for unified handling across the application.
Usually we set a unified exception handling in Spring Boot that only handles exceptions thrown by Controllers. Some requests throw exceptions before reaching the Controller, such as certain Servlet container exceptions, which cannot be captured by the unified handler. This article discusses a solution.
ErrorPageFilter
This type of Whitelabel Error Page appears whenever Spring Boot encounters an error. When testing with Postman, the response looks like:
{
"timestamp": "2021-04-29T22:45:33.231+0000",
"status": 500,
"message": "Internal Server Error",
"path": "foo/bar"
}Spring Boot registers an ErrorPageFilter at startup; when a servlet throws an exception, this filter intercepts it and processes the error according to different strategies: if the error is already being handled, it processes it directly; otherwise it forwards to the appropriate error page.
When a servlet throws an exception, the handling servlet can obtain several attributes from the HttpServletRequest:
These attributes provide detailed information about the exception.
Default Error Page
Normally Spring Boot redirects to /error for handling exceptions, and the logic for /error is implemented by BasicErrorController.
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
// Return error page
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(
getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
// Return JSON
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
// ... other methods omitted
}The corresponding bean configuration is:
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}To replace the default mechanism, we can implement a custom ErrorController and inject it into Spring IoC, effectively overriding BasicErrorController. The custom controller can simply rethrow the error so that it is processed by the unified exception handler.
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class ExceptionController extends AbstractErrorController {
public ExceptionController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
@Override
@Deprecated
public String getErrorPath() {
return null;
}
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
throw new RuntimeException(getErrorMessage(request));
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
throw new RuntimeException(getErrorMessage(request));
}
private String getErrorMessage(HttpServletRequest request) {
Object code = request.getAttribute("javax.servlet.error.status_code");
Object exceptionType = request.getAttribute("javax.servlet.error.exception_type");
Object message = request.getAttribute("javax.servlet.error.message");
Object path = request.getAttribute("javax.servlet.error.request_uri");
Object exception = request.getAttribute("javax.servlet.error.exception");
return String.format("code: %s,exceptionType: %s,message: %s,path: %s,exception: %s",
code, exceptionType, message, path, exception);
}
}By directly throwing an exception, the custom ExceptionController forwards these servlet‑level errors to the unified exception handling mechanism, ensuring a consistent error response across the entire application.
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.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
