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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How Spring Boot Registers Custom Error Pages with Tomcat

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);
  }
}
ErrorPageRegistrarBeanPostProcessor

is 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.

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.

javabackend-developmentSpring BootError HandlingTomcatSpring MVC
Spring Full-Stack Practical Cases
Written by

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.

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.