skip to content

You need to configure something on Spring Boot's embedded Tomcat that no `server.*` property exposes — for example an additional plain-HTTP connector alongside the main one. How do you do it in code, and how does that interact with the `server.*` properties already set?

level: middleimportance: should knowfreq 38%

answer

  1. a hook into the factory, before the server exists
  2. the type parameter names the container
  3. two customizers write to one object
  4. order decides which write survives

basics

~10 s

Register a WebServerFactoryCustomizer<TomcatServletWebServerFactory> bean. It receives the factory before the server is built, so you can call addAdditionalTomcatConnectors or addConnectorCustomizers. It runs after the property-driven customizer, so code overrides server.* values.

solid answer

~50 s

Define a bean of type `WebServerFactoryCustomizer<TomcatServletWebServerFactory>`. Spring Boot applies every such customizer to the factory before the server is created, so anything the factory exposes is reachable: `addAdditionalTomcatConnectors(Connector)` attaches a second listener, `addConnectorCustomizers(TomcatConnectorCustomizer)` reaches connector fields the properties do not surface, and `addContextCustomizers(TomcatContextCustomizer)` reaches the servlet context. Ordering matters: Boot's own property-driven customizer runs at order 0, and a plain bean defaults to lowest precedence, so your code runs last and wins over `server.*` for the same setting. That is convenient but easy to trip over — a value set in code silently overrides the same value in configuration, and someone editing the properties file sees no effect. Prefer properties whenever one exists, keep the customizer narrowly scoped to the setting that has no property, and note the type parameter binds you to Tomcat: swapping the container makes the customizer stop applying.

code

java · 19 lines
java
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EmbeddedTomcatConfig {

    @Bean
    WebServerFactoryCustomizer<TomcatServletWebServerFactory> customizer() {
        return factory -> {
            Connector internal = new Connector("org.apache.coyote.http11.Http11NioProtocol");
            internal.setPort(9090);
            factory.addAdditionalTomcatConnectors(internal);
            factory.addContextCustomizers(context -> context.setUseHttpOnly(true));
        };
    }
}

go deeper

for a junior

Know that server.* properties are the normal way to configure the embedded container, and that a customizer bean exists for the settings they do not cover.

for a middle

Name the bean type, describe when it runs relative to server creation, and explain that an unordered customizer runs after the property-driven one and therefore overrides it.

for a senior

Treat it as a last resort with real cost: hidden overrides that make an incident-time property change do nothing, and silent loss of the customization if the container is ever swapped.

for a principal

Set the house rule — properties are the configuration surface, customizers are narrow and reviewed — so that operational tuning stays externalized rather than compiled into the artifact.

## The extension point Spring Boot's `server.*` properties cover the settings most applications need, but they are deliberately not a complete mapping of everything the container can do. The escape hatch is the web server factory: Boot builds a factory object, hands it to every `WebServerFactoryCustomizer` bean in the context, and only then asks the factory to create and start the server. Anything the factory exposes is therefore configurable in code. ```java import org.apache.catalina.connector.Connector; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ExtraConnectorConfig { @Bean WebServerFactoryCustomizer<TomcatServletWebServerFactory> extraConnector() { return factory -> { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setPort(9090); factory.addAdditionalTomcatConnectors(connector); }; } } ``` ## The three reach-in methods - **`addAdditionalTomcatConnectors(Connector...)`** attaches extra listeners beside the one `server.port` created. The common uses are a second port for internal or management traffic on a different interface, or a plain-HTTP port beside a TLS one. - **`addConnectorCustomizers(TomcatConnectorCustomizer)`** hands you the main connector after Boot has configured it, which is where you set fields with no property equivalent — the sort of low-level connector attribute that exists in Tomcat's own configuration surface but was never promoted to a Spring Boot property. - **`addContextCustomizers(TomcatContextCustomizer)`** hands you the servlet `Context` for context-level settings. Generic factory settings — port, address, compression, error pages — are also available directly on `ConfigurableServletWebServerFactory`, and a customizer typed to that interface keeps working if the container is swapped, at the cost of only reaching container-neutral settings. ## Ordering, and why code wins Boot's own translation of `server.*` into factory calls is itself a customizer, and it declares an explicit order of 0. A customizer bean that implements no ordering gets lowest precedence, so it runs *after* the property-driven one. Since these are all mutating calls on the same factory, last write wins: your code overrides the property. That default is usually what you want — it lets code fill gaps and adjust what properties set. It is also a genuine operational hazard. Someone raises `server.tomcat.threads.max` in configuration to respond to an incident, restarts, and nothing changes, because a customizer buried in a configuration class sets it too. Two defences: keep customizers minimal, touching only the setting that truly has no property; and if a value should remain property-driven, read it from the `Environment` inside the customizer rather than hardcoding it. If you deliberately want the property to win, implement `Ordered` with a value below 0 so your customizer runs first and Boot's overwrites it. ## When not to use it Reach for the customizer only when there is no property. Properties are externalized, visible in one place, overridable per environment without a rebuild, and greppable during an incident; a customizer is compiled code that nobody thinks to look at. A configuration class that reproduces in Java what six `server.tomcat.*` lines would have said is a maintenance liability rather than a technique. Be aware too of what the type parameter commits you to. `WebServerFactoryCustomizer<TomcatServletWebServerFactory>` is matched by type — if the application later swaps to another embedded container, the bean simply never matches and the customization silently disappears. There is no error, and the setting is quietly gone. That is one more reason to prefer properties where they exist, and to have a test asserting the behaviour you needed rather than trusting the bean is being applied. ## Verifying A customizer is easy to write and easy to have no effect. Assert the outcome: start the application on a random port in a test and check the extra listener answers, or check the setting through whatever surface exposes it. "The bean exists" is not evidence that it ran.

  • Why might a value set in application.properties appear to be ignored on an embedded Tomcat?
    Because a `WebServerFactoryCustomizer` bean set the same thing. Boot's property-driven customizer runs at order 0 and an unordered bean runs after it, so code is the last write and wins. Other candidates are a renamed property that is now silently ignored, or a profile-specific file overriding the one you edited. Check the running configuration, not the file you changed.
  • How do you make the property win over your customizer instead?
    Have the customizer implement `Ordered` and return a value below 0 so it runs before Boot's property-driven customizer, which then overwrites it. The cleaner alternative is to read the value from the `Environment` inside the customizer, so there is only one source of truth and the property remains authoritative either way.
  • What happens to a WebServerFactoryCustomizer<TomcatServletWebServerFactory> if the application swaps to a different embedded container?
    It stops being applied. Customizers are matched by the generic type, so a bean typed to the Tomcat factory never matches another container's factory. There is no startup error — the customization just vanishes. Container-neutral settings should be typed to `ConfigurableServletWebServerFactory`, and any behaviour that matters deserves a test.

saying these in an interview costs you the question

  • Assumes properties always take precedence over a customizer bean
  • Rewrites settings in code that already have server.* properties
  • Thinks the customizer runs after the server has started
  • Expects a Tomcat-typed customizer to apply to any embedded container
  • Never verifies the customizer actually took effect

context