skip to content

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?

level: principalimportance: nice to knowfreq 20%

answer

  1. generic bean-to-container-filter bridge, not just security
  2. gives DI/AOP/lifecycle to a filter
  3. FilterRegistrationBean vs DelegatingFilterProxyRegistrationBean
  4. order relative to springSecurityFilterChain
  5. one instance = must be thread-safe

basics

~20 s

Register 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 s

DelegatingFilterProxy 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
java
// 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

for a junior

Know the proxy lets a Spring bean act as a filter so it can be injected.

for a middle

Contrast FilterRegistrationBean and DelegatingFilterProxy and mention ordering.

for a senior

Explain thread-safety and ordering relative to springSecurityFilterChain.

for a principal

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

context