Home Java Adding a Custom Filter to Spring Security Filter Chain: A Complete Guide
Advanced 7 min · July 14, 2026

Adding a Custom Filter to Spring Security Filter Chain: A Complete Guide

Learn how to add a custom filter to the Spring Security filter chain with production-tested patterns.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Java 17+
  • Spring Boot 3.x
  • Basic understanding of Spring Security configuration
  • Familiarity with servlet filters
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use addFilterBefore() or addFilterAfter() to insert your filter at a specific position in the chain.
  • Extend OncePerRequestFilter to ensure your filter executes once per request.
  • Never call FilterChain.doFilter() more than once or forget to call it.
  • Register your filter as a Spring bean to enable dependency injection.
  • For reactive apps, use WebFilter instead of javax.servlet.Filter.
✦ Definition~90s read
What is Adding a Custom Filter to the Spring Security Filter Chain?

A custom filter in Spring Security is a servlet filter that you add to the security filter chain to perform custom logic such as header validation, token extraction, or auditing, by extending OncePerRequestFilter and registering it in the SecurityFilterChain bean.

Think of the Spring Security filter chain as a series of security checkpoints at an airport.
Plain-English First

Think of the Spring Security filter chain as a series of security checkpoints at an airport. Each checkpoint performs a specific check (e.g., ID verification, baggage scan). Adding a custom filter is like adding a new checkpoint—say, a pat-down—at a specific point in the line. You can place it before or after existing checkpoints. The key is to not skip any checkpoints and to ensure your checkpoint doesn't hold up the line indefinitely.

If you've worked with Spring Security for more than a week, you've probably needed to add custom logic to the security filter chain. Maybe you need to extract a tenant ID from a header for multi-tenancy, validate a custom token format, or log every authentication attempt for auditing. The filter chain is the perfect place for such cross-cutting concerns—but only if you know how to hook into it correctly.

I've seen countless production incidents caused by misconfigured filters: filters that fire twice, filters that bypass authentication entirely, filters that throw obscure NullPointerExceptions because they weren't initialized as Spring beans. In this guide, I'll show you the right way to add a custom filter, the traps to avoid, and how to debug when things go wrong.

We'll cover everything from the basics of the filter chain to advanced patterns like conditional filtering, ordering, and testing. By the end, you'll be able to add custom filters with confidence—and maybe even fix a production issue at 2 AM without breaking a sweat.

Understanding the Spring Security Filter Chain

Before you add a custom filter, you need to understand how the filter chain works. Spring Security is built on a chain of servlet filters, each responsible for a specific security concern: authentication, authorization, CSRF protection, session management, and more. When a request comes in, it passes through these filters in a specific order.

The key classes are FilterChainProxy and SecurityFilterChain. FilterChainProxy delegates to one or more SecurityFilterChain instances based on the request. Each SecurityFilterChain contains an ordered list of filters. Spring Boot auto-configures a default chain, but you can customize it by defining a SecurityFilterChain bean.

Here's a typical default filter order (simplified)
  • SecurityContextPersistenceFilter
  • LogoutFilter
  • UsernamePasswordAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • BasicAuthenticationFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor

Your custom filter can be inserted before, after, or at the position of any of these filters using addFilterBefore(), addFilterAfter(), or addFilterAt(). The last one replaces an existing filter at that position, which is risky if you don't know the exact behavior of the original filter.

Production Insight: Never use addFilterAt() unless you are 100% sure you want to replace an existing filter. I've seen teams replace the SecurityContextPersistenceFilter and then wonder why the security context is empty in subsequent filters. If you need to run logic around an existing filter, use addFilterBefore() or addFilterAfter() instead.

SecurityConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(new CustomFilter(), BasicAuthenticationFilter.class);
        return http.build();
    }
}
🔥Filter Order Matters
📊 Production Insight
In Spring Boot 3.1, the default filter order changed slightly due to the new SecurityContextHolderFilter. Always check the actual filter order by enabling debug logging: logging.level.org.springframework.security=DEBUG. This will print the filter chain on startup.
🎯 Key Takeaway
The filter chain is ordered, and your filter's position determines what data is available. Use addFilterBefore() or addFilterAfter() with a known filter class as a reference point.

Creating a Custom Filter: OncePerRequestFilter vs Filter

The most important decision when creating a custom security filter is whether to implement javax.servlet.Filter or extend OncePerRequestFilter. In 99% of cases, you should choose OncePerRequestFilter. Here's why:

  • OncePerRequestFilter guarantees that the filter executes only once per request, even if the request is forwarded or included. Standard Filter can execute multiple times if the request is dispatched to another resource.
  • OncePerRequestFilter provides a convenient doFilterInternal() method that already handles the FilterChain call, so you can't forget to call it (unless you override doFilter() directly).
  • It integrates nicely with Spring's GenericFilterBean, giving you access to initialization parameters and Spring bean lifecycle.

Here's a template for a custom filter that extracts a tenant ID from a header:

```java @Component public class TenantFilter extends OncePerRequestFilter {

@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String tenantId = request.getHeader("X-Tenant-ID"); if (tenantId != null) { TenantContext.setCurrentTenant(tenantId); } try { filterChain.doFilter(request, response); } finally { TenantContext.clear(); } } } ```

Notice the try-finally block. This ensures the tenant context is cleared even if an exception occurs downstream. This pattern is critical for thread-safe cleanup, especially in environments with thread pooling.

Production Insight: I once saw a filter that set a request-scoped attribute but forgot to clear it. In a high-throughput system, this caused memory leaks and eventually OutOfMemoryErrors. Always clean up thread-local or request-scoped data in a finally block.

TenantFilter.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class TenantFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        String tenantId = request.getHeader("X-Tenant-ID");
        if (tenantId != null) {
            TenantContext.setCurrentTenant(tenantId);
        }
        try {
            filterChain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }
}
⚠ Don't Forget the Finally Block
📊 Production Insight
If you need to exclude certain paths from your filter (e.g., health checks), override shouldNotFilter() method. This is cleaner than checking the path inside doFilterInternal().
🎯 Key Takeaway
Extend OncePerRequestFilter for single-execution guarantee and cleaner code. Always clean up resources in a finally block.

Registering Your Filter in the Security Filter Chain

Once you have your filter class, you need to register it in the security configuration. There are two common approaches: manual registration in a SecurityFilterChain bean, or using @Component with auto-detection. I strongly recommend manual registration for clarity and control.

Approach 1: Manual Registration (Preferred)

```java @Configuration @EnableWebSecurity public class SecurityConfig {

@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(new TenantFilter(), BasicAuthenticationFilter.class); return http.build(); } } ```

Approach 2: Using @Component (Risky)

If you annotate your filter with @Component, Spring Boot will automatically register it as a servlet filter in the container's filter chain. But this is outside the Spring Security filter chain. The filter will still execute, but it won't have access to the security context, and it may execute at a different point in the request lifecycle. This can lead to subtle bugs.

Worse, if you also manually add the filter in SecurityFilterChain, it will execute twice—once as a container filter and once as a security filter. This is exactly what caused the production incident I described earlier.

Best Practice: If you need the filter to be part of the security chain, do not annotate it with @Component. Instead, create the filter instance manually or inject it as a bean but register it only in the security config.

```java @Bean public TenantFilter tenantFilter() { return new TenantFilter(); }

@Bean public SecurityFilterChain filterChain(HttpSecurity http, TenantFilter tenantFilter) throws Exception { http.addFilterBefore(tenantFilter, BasicAuthenticationFilter.class); return http.build(); } ```

This way, the filter is a Spring bean (so it can have dependencies injected), but it's only registered in the security chain.

SecurityConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public TenantFilter tenantFilter() {
        return new TenantFilter();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, TenantFilter tenantFilter) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(tenantFilter, BasicAuthenticationFilter.class);
        return http.build();
    }
}
⚠ Avoid @Component for Security Filters
📊 Production Insight
In Spring Boot 3.0+, the default security configuration uses lambda DSL. Make sure your custom filter registration is inside the lambda, not before it, to ensure proper ordering.
🎯 Key Takeaway
Register your filter manually in the SecurityFilterChain bean. If you need DI, declare the filter as a @Bean method and inject it into the security configuration.

Ordering Your Filter Correctly

The position of your filter in the chain determines what information is available and how it interacts with other filters. Spring Security provides several well-known filter classes that you can use as reference points:

  • SecurityContextPersistenceFilter (or SecurityContextHolderFilter in Boot 3.1+): Ensures the SecurityContext is loaded from the session. Place filters that need the authenticated user after this.
  • UsernamePasswordAuthenticationFilter: Processes form logins. Place filters that need to inspect login attempts before this.
  • BasicAuthenticationFilter: Processes basic auth. Place filters that need to validate tokens after this if you want to use the authenticated principal.
  • ExceptionTranslationFilter: Handles authentication exceptions. Place filters that might throw auth exceptions before this so they are properly translated.
  • FilterSecurityInterceptor: The last filter that enforces authorization. Place filters that need to run after authorization checks (e.g., auditing) before this.

Here's how to add a filter after the security context is established:

``java http.addFilterAfter(new AuditFilter(), SecurityContextHolderFilter.class); ``

Production Insight: In Spring Boot 3.1, the SecurityContextPersistenceFilter was replaced by SecurityContextHolderFilter. If you're upgrading, your addFilterAfter calls referencing the old class will break silently—the filter will be placed at the end of the chain, not where you intended. Always check the actual filter class names by enabling debug logging.

SecurityConfigOrder.javaJAVA
1
2
3
4
5
6
http
    .authorizeHttpRequests(authz -> authz
        .requestMatchers("/api/**").authenticated()
    )
    .addFilterAfter(new AuditFilter(), SecurityContextHolderFilter.class)
    .addFilterBefore(new RequestValidationFilter(), BasicAuthenticationFilter.class);
💡Debug Filter Order
📊 Production Insight
If you need to run logic both before and after authentication, consider using two separate filters placed at different positions. Combining too much logic in one filter makes it harder to debug and test.
🎯 Key Takeaway
Use well-known filter classes as reference points for ordering. Be aware of version-specific changes to filter class names.

What the Official Docs Won't Tell You

I've spent years debugging Spring Security filter issues, and here are the gotchas that the official documentation glosses over:

1. The @Component Trap

As mentioned, annotating your filter with @Component registers it as a servlet filter in the container, not in the Spring Security chain. But here's the subtle part: if you also have a SecurityFilterChain bean, the container filter will still execute, but it will run before the security chain. This means your filter might see unauthenticated requests even though the security chain eventually authenticates them. This can cause inconsistent behavior, especially if your filter relies on the security context.

2. addFilterAt() is a Landmine

The addFilterAt() method replaces an existing filter at the specified position. But if the filter class you specify is not in the chain (e.g., because of a configuration change), your filter will be added at the end without warning. This can lead to filters executing in the wrong order. I've seen this happen when upgrading Spring Boot versions where a filter class was renamed.

3. Request Wrapping Pitfalls

If you wrap the request (e.g., to cache the body), you must ensure that the wrapper is passed to filterChain.doFilter(). Many developers forget this and pass the original request, causing the wrapper to be ignored. Also, if you wrap the response, be aware that downstream filters might call getWriter() or getOutputStream() multiple times, which can cause exceptions if your wrapper doesn't support it.

4. Async Requests

OncePerRequestFilter does not guarantee single execution for async requests by default. You need to set isAsyncDispatch() to true if you want the filter to execute on the async dispatch as well. Otherwise, it will only run on the initial request thread.

5. Testing Filters Requires Mock Requests

Unit testing filters is straightforward with MockHttpServletRequest and MockHttpServletResponse. But integration testing with @WebMvcTest can be tricky because the full security chain is not loaded. Use @SpringBootTest with a mock web environment or @AutoConfigureMockMvc to test the entire chain.

RequestCachingFilter.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
public class RequestCachingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
        filterChain.doFilter(wrapper, response);
    }
}
⚠ Always Pass the Wrapper
📊 Production Insight
In Spring Boot 3.2, the SecurityContextHolderFilter is the new default. If you have custom filters referencing SecurityContextPersistenceFilter, they will silently fail to order correctly. Always check the filter chain dump on startup.
🎯 Key Takeaway
The official docs don't cover the @Component trap, addFilterAt() risks, request wrapping, async dispatch, or integration testing pitfalls. Be aware of these to avoid production issues.

Conditional Filter Execution with shouldNotFilter

Often you don't want your filter to run on every request. For example, a tenant filter might be unnecessary for public endpoints. OncePerRequestFilter provides a convenient shouldNotFilter() method that you can override to skip execution based on the request.

``java @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String path = request.getRequestURI(); return path.startsWith("/public/") || path.startsWith("/actuator/"); } ``

This is much cleaner than checking the path inside doFilterInternal() and returning early. It also makes the filter's intent clearer.

Production Insight: Be careful with path matching. If you use request.getRequestURI(), it includes the context path. Use request.getServletPath() if you want the path relative to the context. Also, consider using AntPathMatcher or PathPattern for more complex patterns.

You can also make the exclusion paths configurable via properties:

```java @Value("${app.filter.skip-paths:/public/,/actuator/}") private List<String> skipPaths;

@Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String path = request.getServletPath(); return skipPaths.stream().anyMatch(pattern -> pathMatch(pattern, path)); } ```

This allows you to change the behavior without recompiling.

ConfigurableTenantFilter.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
public class ConfigurableTenantFilter extends OncePerRequestFilter {

    @Value("${app.tenant-filter.skip-paths:/public/**,/actuator/**}")
    private List<String> skipPaths;

    private final PathMatcher pathMatcher = new AntPathMatcher();

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String path = request.getServletPath();
        return skipPaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        String tenantId = request.getHeader("X-Tenant-ID");
        if (tenantId != null) {
            TenantContext.setCurrentTenant(tenantId);
        }
        try {
            filterChain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }
}
💡Use shouldNotFilter for Cleaner Code
📊 Production Insight
If you use @Value in a filter, ensure the filter is a Spring bean. If you instantiate it with new, the @Value fields will be null. Always inject the filter as a bean or use constructor injection.
🎯 Key Takeaway
Use shouldNotFilter() to conditionally skip filter execution. Make exclusion paths configurable for flexibility in different environments.

Testing Custom Filters

Testing custom filters is essential to ensure they work correctly in the security chain. You should write both unit tests and integration tests.

Unit Testing

Unit tests verify the filter logic in isolation. Use MockHttpServletRequest and MockHttpServletResponse to simulate requests.

``java @Test void testTenantFilterSetsContext() throws Exception { TenantFilter filter = new TenantFilter(); MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("X-Tenant-ID", "tenant123"); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = (req, res) -> { assertEquals("tenant123", TenantContext.getCurrentTenant()); }; filter.doFilterInternal(request, response, filterChain); assertNull(TenantContext.getCurrentTenant()); // cleared after } ``

Integration Testing

Integration tests verify that the filter is correctly registered in the security chain. Use @SpringBootTest with TestRestTemplate or MockMvc.

```java @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc class SecurityFilterIntegrationTest {

@Autowired private MockMvc mockMvc;

@Test void testFilterIsApplied() throws Exception { mockMvc.perform(get("/api/secure") .header("X-Tenant-ID", "tenant123")) .andExpect(status().isOk()); // Verify tenant context was set (e.g., via a test endpoint that returns the tenant) } } ```

Production Insight: When testing with @WebMvcTest, the security auto-configuration is not loaded. Use @SpringBootTest to test the full security chain. Also, be aware that MockMvc does not actually send HTTP requests, so filters that rely on request attributes set by the container may not work as expected.

TenantFilterTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;

class TenantFilterTest {

    @Test
    void testTenantFilterSetsAndClearsContext() throws ServletException, IOException {
        TenantFilter filter = new TenantFilter();
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-Tenant-ID", "tenant123");
        MockHttpServletResponse response = new MockHttpServletResponse();
        FilterChain filterChain = (req, res) -> {
            assertEquals("tenant123", TenantContext.getCurrentTenant());
        };
        filter.doFilterInternal(request, response, filterChain);
        assertNull(TenantContext.getCurrentTenant());
    }
}
🔥Integration Test with @SpringBootTest
📊 Production Insight
If your filter uses request attributes set by the container (e.g., servlet path), MockMvc may not set them correctly. Consider using TestRestTemplate with a real server for end-to-end testing.
🎯 Key Takeaway
Unit test the filter logic with mock request/response. Integration test the full chain with @SpringBootTest and MockMvc or TestRestTemplate.
● Production incidentPOST-MORTEMseverity: high

The Double-Filter Disaster at a Fintech Startup

Symptom
Users were intermittently logged out. The app showed '403 Forbidden' on valid JWT tokens. Logs showed duplicate authentication attempts for the same request.
Assumption
The developer assumed the filter was a standard javax.servlet.Filter and didn't extend OncePerRequestFilter. They thought the container would handle invocation.
Root cause
The filter was registered both as a @Component and manually added to the filter chain via HttpSecurity. Spring Boot's auto-configuration picked it up as a servlet filter, and the security configuration added it again, causing it to execute twice.
Fix
Removed the @Component annotation and kept only the explicit registration in the security config. Changed the filter to extend OncePerRequestFilter to guarantee single execution per request even if registered multiple times.
Key lesson
  • Always extend OncePerRequestFilter for custom security filters.
  • Avoid mixing automatic detection (@Component) with manual registration.
  • Use logging at the start and end of your filter to trace execution count.
  • Test filter invocation count in integration tests with MockHttpServletRequest.
  • Document your filter registration approach in your team's security guidelines.
Production debug guideSymptom to Action4 entries
Symptom · 01
Filter not executing at all
Fix
Check that the filter is registered in SecurityFilterChain bean. Verify the filter class is not abstract and has no constructor issues. Ensure the filter is not being skipped due to a mismatched request matcher.
Symptom · 02
Filter executes multiple times per request
Fix
Check for duplicate registration (both @Component and manual). Ensure filter extends OncePerRequestFilter. Look for forward() or include() calls that might re-trigger the filter.
Symptom · 03
NullPointerException in filter when accessing SecurityContext
Fix
Verify the filter is executed after the SecurityContextPersistenceFilter. Use addFilterAfter() with the appropriate filter class. Check that SecurityContextHolder is properly populated.
Symptom · 04
Filter blocks all requests unexpectedly
Fix
Review the filter logic for missing chain.doFilter() call. Check for exceptions that are not caught. Ensure filter doesn't consume the request stream without buffering.
★ Quick Debug Cheat SheetImmediate actions for common filter issues
Filter not invoked
Immediate action
Add a breakpoint or log at the beginning of doFilterInternal()
Commands
curl -v http://localhost:8080/api/test
grep 'CustomFilter' /var/log/app.log
Fix now
Ensure filter is added to SecurityFilterChain and request path matches.
Filter invoked twice+
Immediate action
Check for @Component on filter class
Commands
grep -r '@Component' CustomFilter.java
Check SecurityConfig for duplicate addFilter* calls
Fix now
Remove @Component and keep only manual registration.
SecurityContext null in filter+
Immediate action
Check filter position relative to SecurityContextPersistenceFilter
Commands
Add log: SecurityContextHolder.getContext().getAuthentication()
Verify filter order in SecurityFilterChain
Fix now
Use addFilterAfter(myFilter, SecurityContextPersistenceFilter.class)
FeatureOncePerRequestFilterjavax.servlet.Filter
Single execution per requestYesNo
Spring integrationGenericFilterBeanNone
Cleanup supportdoFilterInternal with try-finallyManual
Async supportConfigurableManual
Recommended for securityYesNo
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
SecurityConfig.java@ConfigurationUnderstanding the Spring Security Filter Chain
TenantFilter.java@ComponentCreating a Custom Filter
SecurityConfigOrder.javahttpOrdering Your Filter Correctly
RequestCachingFilter.javapublic class RequestCachingFilter extends OncePerRequestFilter {What the Official Docs Won't Tell You
ConfigurableTenantFilter.java@ComponentConditional Filter Execution with shouldNotFilter
TenantFilterTest.javaclass TenantFilterTest {Testing Custom Filters

Key takeaways

1
Always extend OncePerRequestFilter for custom security filters to guarantee single execution per request.
2
Register filters manually in the SecurityFilterChain bean; avoid @Component to prevent double execution.
3
Use addFilterBefore() or addFilterAfter() with well-known filter classes for correct ordering.
4
Clean up thread-local state in a finally block to prevent memory leaks and cross-request contamination.
5
Test filters with both unit tests (mock request/response) and integration tests (@SpringBootTest with MockMvc).
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain how to add a custom filter to the Spring Security filter chain. ...
Q02JUNIOR
What is the difference between OncePerRequestFilter and Filter, and when...
Q03SENIOR
How would you debug a custom filter that is not executing at all in prod...
Q01 of 03SENIOR

Explain how to add a custom filter to the Spring Security filter chain. What are the key methods and considerations?

ANSWER
You define a class that extends OncePerRequestFilter and override doFilterInternal(). Then in your SecurityFilterChain bean, use addFilterBefore(), addFilterAfter(), or addFilterAt() to insert it at the desired position. Key considerations: avoid @Component to prevent double execution, ensure proper ordering relative to other filters, clean up thread-local state in a finally block, and test both in isolation and integration.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is the difference between OncePerRequestFilter and Filter?
02
How do I add a custom filter before the authentication filter?
03
Why is my custom filter executing twice?
04
Can I use @Autowired in a custom filter?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Spring Security. Mark it forged?

7 min read · try the examples if you haven't

Previous
Session Management in Spring Security: Concurrency, Timeout, and Fixation Protection
26 / 31 · Spring Security
Next
Handling Spring Security Exceptions with @ExceptionHandler and AccessDeniedHandler