How does Spring Boot actually apply WebServerFactoryCustomizer beans, and how does ordering interact with property-based configuration?
answer
- WebServerFactoryCustomizerBeanPostProcessor
- filter by generic type (ResolvableType), sort by @Order
- Boot property binding = order 0
- unordered = LOWEST_PRECEDENCE = runs last = wins
- customizer layers on top; own @Bean replaces + loses binding
basics
~20 sA WebServerFactoryCustomizerBeanPostProcessor collects all matching customizer beans, sorts them by Ordered/@Order, and calls customize() on the factory during its initialization. Boot's own property-binding customizer has order 0; unordered custom ones run after it, so their setters override properties.
solid answer
~40 sBoot registers a WebServerFactoryCustomizerBeanPostProcessor. When the WebServerFactory bean is initialized, that post-processor looks up all WebServerFactoryCustomizer beans whose generic type is assignable to the factory, sorts them with AnnotationAwareOrderComparator (Ordered / @Order), and invokes customize() in order. Boot binds server.* properties through its own ServletWebServerFactoryCustomizer, which has order 0. A customizer with no explicit order defaults to lowest precedence, so it runs last and its setters win over the property-bound values. If you deliberately need to run before Boot's binding — rare — give your customizer a negative order. Because customization happens in a BeanPostProcessor keyed to the factory bean, it fires exactly once, before getWebServer() builds the server. Ordering only matters when two customizers set the same overlapping property.
code
java · 27 linesimport org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
// No @Order -> LOWEST_PRECEDENCE -> runs AFTER Boot's order-0 property binding.
// So this setPort overrides server.port from application.properties.
@Component
class OverridePortCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setPort(9000);
}
}
// Negative order -> runs FIRST -> Boot's binding runs later and wins.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class DefaultsFirstCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setPort(8081); // overwritten by server.port if that property is set
}
}go deeper
Likely only knows customizers exist; not expected to explain the post-processor or ordering.
Should know a BeanPostProcessor applies them before startup and that @Order exists.
Should explain the type filter, the sort, and that unordered customizers run after Boot's order-0 property binding and therefore override it.
Should weigh customizer-vs-own-factory-bean trade-offs, reason precisely about last-writer-wins ordering, and know how to force pre/post relative to Boot's binding.
## The application mechanism The glue is `org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor` — a Spring **BeanPostProcessor**. It is registered by `ServletWebServerFactoryAutoConfiguration` (and the reactive equivalent) via a `BeanPostProcessorsRegistrar`. A BeanPostProcessor's `postProcessBeforeInitialization` runs against every bean during its init phase. This one checks: *is this bean a `WebServerFactory`?* If yes, it: 1. **Lazily** looks up all beans of type `WebServerFactoryCustomizer` from the context (lazy to avoid premature instantiation). 2. **Filters** them by generic type: a `WebServerFactoryCustomizer<T>` is applied only if the factory is assignable to `T` (resolved via `ResolvableType`). So a `WebServerFactoryCustomizer<TomcatServletWebServerFactory>` only runs for Tomcat; a `WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>` runs for any servlet factory. 3. **Sorts** the survivors with `AnnotationAwareOrderComparator` (honours the `Ordered` interface, `@Order`, and `@Priority`). 4. Calls `customize(factory)` on each, in that order, **before** the factory's own initialization completes — hence before `getWebServer()` creates the server. ## Ordering and property interaction — the key insight Boot binds all your `server.*` properties using an internal customizer, `ServletWebServerFactoryCustomizer`, annotated effectively with **order 0**. (There's also `TomcatWebServerFactoryCustomizer` etc. for container-specific properties, also low order.) `AnnotationAwareOrderComparator` sorts **ascending** by order value: lower value = earlier. A customizer that implements no ordering gets `Ordered.LOWEST_PRECEDENCE` (Integer.MAX_VALUE), so it sorts **last**. Since these are setter calls, **the last writer wins**. Therefore: - An unordered custom customizer runs *after* Boot's order-0 property binding → your `setPort(9000)` overrides `server.port` from properties. - If you (unusually) want Boot's property binding to win, give your customizer a **negative order** so it runs first and is then overwritten. ```java @Order(Ordered.HIGHEST_PRECEDENCE) // runs first; boot's order-0 binding runs after and wins class EarlyCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> { ... } ``` ## Two ways to register — and why customizer is preferred You *could* instead declare your own `ConfigurableServletWebServerFactory` `@Bean`, fully configured. But that **replaces** the auto-configured factory and you **lose** Boot's automatic `server.*` property binding (Boot won't post-process a factory you built and returned as fully-formed unless it still goes through the post-processor — and even then you've bypassed the auto-config bean). The customizer approach layers on top of Boot's binding, keeping properties working. That's why the customizer is the recommended seam. ## Timing and lifecycle - Runs during factory bean initialization, exactly once. - The `WebServer` (and its bound port) does **not** exist yet — you cannot read the live port here; use `WebServerInitializedEvent` for that. - Only fires for embedded servers (there is a factory bean); irrelevant to WAR-in-external-container deployments. ## Gotchas - Getting the direction of ordering wrong: remember **last-applied setter wins**, and unordered = last. - Assuming a `<TomcatServletWebServerFactory>` customizer will run when you've switched to Jetty — it silently won't (type filter excludes it). - Multiple customizers touching the same setter is the only case ordering matters; for disjoint settings order is irrelevant.
- You set server.port=8080 in properties but your unordered customizer calls setPort(9000). What port does the app bind to, and why?9000. Boot's property-binding customizer runs at order 0, and the unordered custom customizer defaults to LOWEST_PRECEDENCE, so it runs last. Setter calls are last-writer-wins, so setPort(9000) overwrites the bound 8080.
- What decides whether a given WebServerFactoryCustomizer is even considered for a factory?Its generic type parameter T. The post-processor resolves T via ResolvableType and only applies the customizer if the factory instance is assignable to T. A <TomcatServletWebServerFactory> customizer is skipped if you're running Jetty.
saying these in an interview costs you the question
- Saying properties always win over programmatic customizers
- Getting order direction backwards (thinking lowest precedence runs first)
- Thinking every customizer runs regardless of its generic type
- Claiming customization happens after the server starts