Appearance
Spring Boot: Filters vs Interceptors | Advantages and Use Cases
Introduction: The Security Layers of an Airport
Think of a busy international airport. Before you even reach your departure gate (the controller), you pass through multiple checkpoints:
The outer entrance — a general security check that applies to every single person entering the airport building, regardless of which airline they are flying. This is the Filter.
The airline specific gate agent — once you are inside and at your specific terminal, the gate agent checks your boarding pass for this airline's requirements. This is the Interceptor.
Both checkpoints inspect the same person (the HTTP request), but they exist at different points in the journey and serve different purposes.
One Line Definitions
| Concept | Definition |
|---|---|
| Filter | Intercepts HTTP requests and responses before they reach the servlet |
| Interceptor | Specific to the Spring framework; intercepts HTTP requests and responses after the servlet is chosen but before they reach the controller |
Read those again after finishing this chapter — they will make much more sense.
The Full Request Journey
When an HTTP request arrives at a Spring Boot application, it follows this path:
HTTP Request
↓
[Servlet Container — Tomcat]
↓
[Filter 1] → [Filter 2] → [Filter N] ← FILTERS live here
↓
[Dispatcher Servlet chosen]
↓
[Interceptor 1] → [Interceptor 2] ← INTERCEPTORS live here
↓
[Controller Method]
↓
[Interceptor 2] → [Interceptor 1] ← post-processing (reverse order)
↓
[Filter N] → [Filter 2] → [Filter 1] ← response filtering (reverse order)
↓
HTTP ResponseWhat Is a Servlet?
A servlet is a plain Java class that accepts an incoming request, processes it, and returns a response. A single application can have multiple servlets:
- One servlet for REST APIs
- One servlet for SOAP APIs
- One servlet for file uploads
- One servlet for static resources
In a Spring Boot microservice, this need is dramatically reduced. The Dispatcher Servlet — Spring Boot's default servlet — handles everything (/* by default). It takes any incoming request, figures out which controller should handle it, creates an instance, and invokes the correct method.
Where Filters Fit
Filters exist inside the servlet container but before any specific servlet is chosen. Because they run at the container level, they are applied to every request regardless of which servlet will eventually handle it.
Where Interceptors Fit
Interceptors are a Spring framework concept. They exist inside the Dispatcher Servlet, between the servlet and the controller. They only intercept requests that the Dispatcher Servlet handles — they cannot intercept requests going to other servlets.
When to Use Each
| Use Case | Filter or Interceptor? | Reason |
|---|---|---|
| Spring Security (authentication/authorization) | Filter | Must apply to all requests, regardless of servlet |
| CORS headers | Filter | Generic, cross servlet concern |
| Request logging for all APIs | Filter | Servlet agnostic logging |
| Rate limiting at the network level | Filter | Applied before any business logic |
| Authentication specific to your Spring controllers | Interceptor | Specific to Dispatcher Servlet |
| Logging with Spring context (access to beans) | Interceptor | Has access to Spring's HandlerMethod |
| Caching logic tied to controller behavior | Interceptor | Needs knowledge of which controller will run |
Rule of thumb:
- Logic that is generic and must apply to all servlets → Filter
- Logic that is specific to your Spring Boot application and its controllers → Interceptor
Creating a Filter
Filters implement Java's Filter interface and have three lifecycle methods:
java
// MyFilter.java
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Called once when the filter object is created
// Use this for one-time initialization (e.g., loading config)
System.out.println("MyFilter initialized");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// Called for every request matching this filter's URL pattern
System.out.println("MyFilter: before processing request");
// Pass request to the next filter in the chain (or to the servlet if no more filters)
chain.doFilter(request, response);
System.out.println("MyFilter: after processing response");
}
@Override
public void destroy() {
// Called once when the filter is removed from service
System.out.println("MyFilter destroyed");
}
}Registering Filters with Ordering
java
// AppConfig.java
@Configuration
public class AppConfig {
@Bean
public FilterRegistrationBean<MyFilter> myFilter1Registration() {
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.addUrlPatterns("/*"); // applies to all URLs
registrationBean.setOrder(1); // lower number = higher priority
return registrationBean;
}
@Bean
public FilterRegistrationBean<MySecondFilter> myFilter2Registration() {
FilterRegistrationBean<MySecondFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MySecondFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(2);
return registrationBean;
}
}Note: If you do not use FilterRegistrationBean and instead put @Component on the filter, you cannot control URL patterns or ordering. Use FilterRegistrationBean for production code.
Creating an Interceptor
Interceptors implement HandlerInterceptor and have three methods:
java
// MyInterceptor.java
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// Runs BEFORE the controller method
// Return true → continue processing
// Return false → stop here; controller and subsequent interceptors are NOT called
System.out.println("Interceptor 1: preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// Runs AFTER the controller method, but only if no exception was thrown
System.out.println("Interceptor 1: postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// Runs ALWAYS after the complete request, even if an exception occurred
// Similar to a finally block
System.out.println("Interceptor 1: afterCompletion");
}
}Registering Interceptors with Ordering
java
// WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor1;
@Autowired
private MySecondInterceptor myInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Order is determined by registration sequence
registry.addInterceptor(myInterceptor1)
.addPathPatterns("/api/**") // apply to these URLs
.excludePathPatterns("/api/public"); // exclude these URLs
registry.addInterceptor(myInterceptor2)
.addPathPatterns("/api/**");
}
}Request and Response Flow with Multiple Filters and Interceptors
With two filters and two interceptors, the complete execution order looks like this:
Incoming request:
Filter1.doFilter— beforechain.doFilter()Filter2.doFilter— beforechain.doFilter()Interceptor1.preHandleInterceptor2.preHandle- Controller method executes
Interceptor2.postHandleInterceptor1.postHandleInterceptor2.afterCompletionInterceptor1.afterCompletionFilter2.doFilter— afterchain.doFilter()Filter1.doFilter— afterchain.doFilter()
Key insight: The response path is the reverse of the request path. The last interceptor/filter to run on the way in is the first to run on the way out.
postHandle vs afterCompletion
postHandle | afterCompletion | |
|---|---|---|
| When it runs | After controller, but only on success | Always, even if an exception was thrown |
| Java equivalent | Normal code after method call | finally block |
| Receives exception? | No | Yes (as parameter) |
| Use for | Modifying the model/response before rendering | Cleanup, resource release, guaranteed logging |
preHandle Returning False
When preHandle returns false, the request processing stops immediately:
java
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !isValid(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false; // stops everything — controller is never called
}
return true;
}Accessing Request and Response
Both filters and interceptors have access to the raw HttpServletRequest and HttpServletResponse. You can read headers, modify the response, add attributes, and more.
java
// Reading a header in a filter
String authHeader = ((HttpServletRequest) request).getHeader("Authorization");
// Adding a response header in an interceptor
response.addHeader("X-Request-Id", UUID.randomUUID().toString());
// Setting a request attribute to pass data to the controller
request.setAttribute("userId", parsedUserId);Interceptors Have Access to Spring Context
One key advantage interceptors have over filters: they receive the handler object, which is the HandlerMethod representing the controller method that will be invoked. This gives you access to Spring's full context — including method annotations, parameter types, and the bean itself.
java
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
// Check if this method has a specific annotation
MyCustomAnnotation ann = method.getMethodAnnotation(MyCustomAnnotation.class);
if (ann != null) {
// act based on annotation value
}
}
return true;
}Filters do not have this capability — they have no knowledge of Spring's routing or annotation infrastructure.
Summary Comparison
| Dimension | Filter | Interceptor |
|---|---|---|
| Framework | Java Servlet API (Jakarta EE) | Spring MVC |
| Position | Before servlet assignment | After servlet, before controller |
| Applies to | All servlets | Only Dispatcher Servlet |
| Can short circuit? | Yes | Yes (preHandle returns false) |
| Access to Spring beans | Not directly | Yes |
| Access to handler method | No | Yes |
| Ordering | setOrder() on FilterRegistrationBean | Registration sequence |
| Use case | Security, CORS, logging at network level | App specific logic, auth, caching |
Interview Questions
Q1. What is the key difference between a Filter and an Interceptor? A Filter runs at the servlet container level before any servlet is selected — it applies to every request regardless of which servlet handles it. An Interceptor runs inside the Dispatcher Servlet, after the servlet is chosen, but before the request reaches the controller. Interceptors are specific to Spring MVC's Dispatcher Servlet.
Q2. When would you choose a Filter over an Interceptor? Choose a Filter when the logic must apply to all servlets (not just the Dispatcher Servlet), when you are implementing something servlet agnostic like Spring Security, CORS headers, or general request/response transformation. Choose an Interceptor when your logic is specific to Spring MVC controllers and you need access to Spring's handler method metadata.
Q3. What is the difference between postHandle and afterCompletion in an interceptor?postHandle runs after the controller method executes successfully but is skipped if an exception is thrown. afterCompletion always runs, even when an exception occurs — like a finally block. Use afterCompletion for cleanup that must always happen.
Q4. What happens when preHandle returns false? The request processing stops immediately. The controller is never called, and subsequent interceptors' preHandle methods are not invoked either. The response must be set explicitly (e.g., setting a 401 status code) before returning false.
Q5. Can you have multiple filters and interceptors? How is their order determined? Yes. Filter order is set via setOrder() on FilterRegistrationBean — lower numbers run first on the way in. Interceptor order is determined by registration sequence in addInterceptors() — the first registered runs first on the way in. Both reverse on the way out.
Q6. Why does Spring Security use filters instead of interceptors? Spring Security needs to intercept every HTTP request at the earliest possible point, regardless of which servlet handles it. Using filters ensures security checks happen before the request even reaches the Dispatcher Servlet — making it impossible to bypass security through non Spring servlets.
Q7. What is a Dispatcher Servlet? The Dispatcher Servlet is Spring MVC's front controller. It accepts all incoming HTTP requests (by default mapped to /*), determines which controller and method should handle each request, invokes the handler, and coordinates the response. It is the central point through which all Spring MVC request processing flows.
Q8. Can a filter access Spring beans? Not directly through @Autowired unless the filter is itself a Spring bean (annotated with @Component). If you register the filter through FilterRegistrationBean while creating a new MyFilter(), it is not managed by Spring. However, if you annotate the filter class with @Component and let Spring create it, Spring beans can be autowired into it.
Q9. What interface must a filter implement? What are its three methods? A filter must implement jakarta.servlet.Filter (or javax.servlet.Filter). Its three methods are: init(FilterConfig) — called once on initialization; doFilter(ServletRequest, ServletResponse, FilterChain) — called for every matching request; and destroy() — called once when removed from service.
Q10. How do you restrict an interceptor to specific URL patterns? In the addInterceptors method of WebMvcConfigurer, after adding the interceptor you can chain .addPathPatterns("/api/**") to specify which patterns it applies to, and .excludePathPatterns("/api/public") to exclude specific paths.