How Spring Boot Registers Custom Error Pages with Tomcat
This article explains how Spring Boot registers custom error pages with its embedded Tomcat container, covering default HTML/JSON error responses, the underlying auto‑configuration classes, BeanPostProcessor registration, and the BasicErrorController that serves the default /error endpoint.
Environment
Environment: Spring Boot 2.4.11
Environment Configuration
The following demonstration uses the interface shown below.
@RestController
@RequestMapping("/exceptions")
public class ExceptionsController {
@GetMapping("/index")
public Object index(int a) {
if (a == 0) {
throw new BusinessException();
}
return "exception";
}
}Default Error Output
By default, when an exception occurs while requesting an interface, two kinds of error responses are returned depending on the Accept request header.
HTML response:
JSON response:
The response format is determined by the Accept header.
Standard Web Error Page Configuration
In a traditional Java web project, error pages are configured in web.xml as follows:
<error-page>
<location>/error</location>
</error-page>After this configuration, the container automatically forwards to the error page when an exception occurs.
Spring Implementation Principle
Spring Boot does not use web.xml, and the Servlet API does not provide an API for error‑page configuration. Spring Boot achieves this through its embedded servlet containers (Tomcat, Undertow, Jetty), with Tomcat as the default.
Servlet Web Service Auto‑Configuration
@EnableConfigurationProperties(ServerProperties.class)
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ...})
public class ServletWebServerFactoryAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}The class implements ErrorPageRegistry, allowing it to register error pages.
BeanPostProcessorsRegistrar
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class, WebServerFactoryCustomizerBeanPostProcessor::new);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
}
} ErrorPageRegistrarBeanPostProcessoris the key component for configuring custom error pages.
ErrorPageRegistrarBeanPostProcessor
public class ErrorPageRegistrarBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ErrorPageRegistry) {
postProcessBeforeInitialization((ErrorPageRegistry) bean);
}
return bean;
}
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
for (ErrorPageRegistrar registrar : getRegistrars()) {
registrar.registerErrorPages(registry);
}
}
private Collection<ErrorPageRegistrar> getRegistrars() {
if (this.registrars == null) {
this.registrars = new ArrayList<>(this.beanFactory.getBeansOfType(ErrorPageRegistrar.class, false, false).values());
this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE);
this.registrars = Collections.unmodifiableList(this.registrars);
}
return this.registrars;
}
}Registering Error Pages
The error page registration occurs in ErrorPageRegistrarBeanPostProcessor. The ErrorMvcAutoConfiguration provides an ErrorPageRegistrar implementation:
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class})
public class ErrorMvcAutoConfiguration {
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
@Override
public int getOrder() {
return 0;
}
}
}Key line:
errorPageRegistry.addErrorPages(errorPage);AbstractConfigurableWebServerFactory
public abstract class AbstractConfigurableWebServerFactory {
private Set<ErrorPage> errorPages = new LinkedHashSet<>();
public void addErrorPages(ErrorPage... errorPages) {
this.errorPages.addAll(Arrays.asList(errorPages));
}
}Tomcat Registration of Error Pages
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory {
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
for (ErrorPage errorPage : getErrorPages()) {
org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}
}
}Spring Boot Default Error Page
Spring Boot provides an auto‑configured error page at /error via ErrorMvcAutoConfiguration:
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class})
public class ErrorMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@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()));
}
}The controller handles both HTML and other media types:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@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);
}
@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);
}
}These two methods produce different responses based on the Accept header, completing the error‑handling flow in Spring Boot.
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.
