You need a stateful, dependency-injected custom servlet filter that runs across the whole app. How does DelegatingFilterProxy help, and what design trade-offs apply?
answer
- generic bean-to-container-filter bridge, not just security
- gives DI/AOP/lifecycle to a filter
- FilterRegistrationBean vs DelegatingFilterProxyRegistrationBean
- order relative to springSecurityFilterChain
- one instance = must be thread-safe
basics
~20 sRegister a DelegatingFilterProxy pointing at your Spring-managed filter bean. The container manages the proxy while Spring injects and lifecycle-manages your real filter, so it gets dependencies and AOP even though the container owns filter registration.
solid answer
~50 sDelegatingFilterProxy is the general-purpose mechanism (not just for security) to make a Spring bean act as a container filter. You define your filter as a Spring bean so it gets constructor injection, config properties, and AOP, then register a DelegatingFilterProxy with the container whose targetBeanName is that bean. The container owns the proxy's lifecycle; Spring owns the delegate's. In Boot you can achieve the same with a FilterRegistrationBean wrapping a bean directly, which is often simpler and gives explicit ordering. Trade-offs: DelegatingFilterProxy defers to context readiness and searches the root context, which matters in multi-context apps; FilterRegistrationBean is more explicit about order and dispatcher types. Keep the filter idempotent and thread-safe since one instance serves all requests, and be deliberate about where it sits relative to springSecurityFilterChain to avoid running before authentication when you didn't intend to.
code
java · 36 lines// A Spring-managed, DI'd filter bridged into the container filter chain.
@Component("correlationIdFilter")
public class CorrelationIdFilter extends OncePerRequestFilter {
private final CorrelationIdGenerator generator; // injected Spring bean
public CorrelationIdFilter(CorrelationIdGenerator generator) {
this.generator = generator;
}
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ServletException, IOException {
String id = generator.next();
MDC.put("correlationId", id); // per-request state, cleared in finally
res.setHeader("X-Correlation-Id", id);
try {
chain.doFilter(req, res);
} finally {
MDC.remove("correlationId");
}
}
}
@Configuration
class FilterConfig {
// Bridge the Spring bean into the container via DelegatingFilterProxy, before security.
@Bean
DelegatingFilterProxyRegistrationBean correlationIdProxy() {
var reg = new DelegatingFilterProxyRegistrationBean("correlationIdFilter");
reg.addUrlPatterns("/*");
reg.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 10); // runs before springSecurityFilterChain
return reg;
}
}go deeper
Know the proxy lets a Spring bean act as a filter so it can be injected.
Contrast FilterRegistrationBean and DelegatingFilterProxy and mention ordering.
Explain thread-safety and ordering relative to springSecurityFilterChain.
Reason about SecurityContext teardown timing, addFilterAfter vs container ordering, multi-context resolution, and when a filter is even the right tool.
## The core capability `DelegatingFilterProxy` is Spring's generic bridge to let **any** Spring bean implementing `jakarta.servlet.Filter` participate in the container's filter chain — Spring Security is just its most famous consumer. This lets a filter enjoy Spring features the container cannot provide: - Constructor/field dependency injection. - `@ConfigurationProperties`, `Environment`, profiles. - AOP (metrics, tracing). - Spring bean lifecycle (`@PostConstruct`, `DisposableBean`), scoping. ## Two ways to register a Spring bean as a filter in Boot 1. **FilterRegistrationBean** (usually preferred in Boot): ```java @Bean FilterRegistrationBean<CorrelationIdFilter> correlationIdFilter(CorrelationIdFilter filter) { FilterRegistrationBean<CorrelationIdFilter> reg = new FilterRegistrationBean<>(filter); reg.addUrlPatterns("/*"); reg.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 10); // before security reg.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC); return reg; } ``` This takes the actual bean instance, so DI is already done; ordering and dispatcher types are explicit. No lazy bean-name lookup involved. 2. **DelegatingFilterProxyRegistrationBean** (bean-name indirection): ```java @Bean DelegatingFilterProxyRegistrationBean correlationProxy() { DelegatingFilterProxyRegistrationBean reg = new DelegatingFilterProxyRegistrationBean("correlationIdFilter"); reg.addUrlPatterns("/*"); reg.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 10); return reg; } ``` Here the registration references the bean by **name**, and resolution is deferred/lazy through a `DelegatingFilterProxy`. Useful when the filter bean is defined elsewhere/late, or in mixed legacy setups. ## Choosing between them - Prefer `FilterRegistrationBean` when you have the bean in hand and want explicit, eager wiring — simpler and clearer. - Prefer `DelegatingFilterProxy`/its registration bean when: you need the deferred lookup semantics, you're in a non-Boot/legacy servlet environment, or the filter must be resolved from a specific context by name. ## Ordering relative to Spring Security The filter's `order` decides whether it runs **before or after** `springSecurityFilterChain` (which sits at `SecurityProperties.DEFAULT_FILTER_ORDER`). A correlation-id/logging filter usually wants to run **before** security so even rejected requests get an id; an audit filter that needs the authenticated principal must run **after** (higher order) — but note the `SecurityContext` is cleared when `FilterChainProxy` returns, so post-security filters that need the principal often must instead be placed **inside** the security chain via `HttpSecurity.addFilterAfter(...)`. This is a key subtlety: 'after security' at the container level means after the context is already torn down. ## Thread-safety One filter instance serves all concurrent requests. Any 'stateful' behavior must be per-request (use request attributes, `ThreadLocal` cleared in `finally`, or scoped beans) — never instance mutable fields shared across requests. ## Multi-context caveat `DelegatingFilterProxy` resolves from the root `WebApplicationContext` by default. In a classic root + DispatcherServlet child topology, ensure the target bean is in the searched context or set `contextAttribute`. ## When to reach for this at all Most cross-cutting concerns (tracing, correlation ids, request logging) are legitimate filter use-cases. But consider whether a Spring MVC `HandlerInterceptor` or a `WebFilter` (WebFlux) or an OncePerRequestFilter placed in the security chain fits better. Use `DelegatingFilterProxy` specifically when you need a **container-level** filter that must be a **Spring bean**.
- Your audit filter needs the authenticated principal but runs as a container filter ordered after security and finds none. Why, and how do you fix it?FilterChainProxy clears the SecurityContextHolder when it returns, so a container-level filter after it sees no authentication. Place the filter inside the security chain via HttpSecurity.addFilterAfter(...) instead.
- When would you choose FilterRegistrationBean over DelegatingFilterProxy in Boot?When you already have the bean instance and want explicit, eager wiring with clear order/dispatcher-type control; DelegatingFilterProxy's bean-name indirection and deferred lookup are only needed for late/other-context resolution or legacy servlet setups.
saying these in an interview costs you the question
- Storing per-request state in instance fields of the filter
- Assuming a container filter ordered after security can read the authenticated principal
- Thinking DelegatingFilterProxy is security-specific and can't wrap arbitrary beans