JAVA: The Difference Between Filter and Interceptor in Spring, and How to Use Them Correctly
My article is open to everyone; non-member readers can click this link to read the full text.
>> Introduction
Since we started working with Spring, we often hear about filters and interceptors. However, when it comes time to use them, we might get confused about their differences and similarities. The main reason for this confusion is that both serve similar purposes (such as authorization checks, log processing, data compression/decompression, etc.).
Scenarios that can be implemented using filters can also be implemented with interceptors, making the boundaries between them quite blurred. To clarify their differences and similarities, let’s delve into the origins and design philosophies of both.
The explanation is based on SpringBoot version 2.7.5.
>> Filter: A Foreign Import
1. Basic Concept
Upon closer examination of the source code, we discover that the concept of a filter is actually a foreign import from Servlets, adhering to the Servlet specification. Here, we can look at the fully qualified name of the Filter class:
javax.servlet.FilterIt can be seen that Filter is used in web containers such as Tomcat for Servlet-related processing and is not originally a tool from Spring. This discovery helps us understand why Filters and Interceptors in Spring have such similar functionalities.
Since they were created by different authors for their respective systems, it's understandable that they arrived at similar ideas and approaches. After all, great minds think alike.
Subsequently, Spring introduced and made compatible the processing logic of Tomcat containers, placing two similar concepts within the same application context (note that Spring did not merge them, just made them compatible), which understandably leads to confusion among developers.
To better understand the role of Filter, let's introduce the official comment for clarification:
A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
From this definition, we can extract two useful pieces of information:
- Timing of Execution: The execution timing of a
Filteris twofold, before the request to a resource is processed and before the response from a resource is returned. - Content of Execution: Essentially, a filter performs a filtering task, and the filtering conditions are determined based on the request to a resource or the response from a resource.
Besides the above information, combining this with the structural design of the Servlet container in Tomcat, we can derive the following process flow diagram for the execution of a Filter:

In real-world development scenarios, preprocessing of resource requests or postprocessing of resource responses might not be limited to a single type of filtering task.
Therefore, Tomcat uses the Chain of Responsibility pattern in its design to handle scenarios where multiple different types of filters are needed to process requests or responses.
This concept is also reflected in the flow diagram mentioned earlier. It’s important to note that because a linear data structure, the chain structure, is used, there is an inherent execution order in the actual process of filter operation. This means that when implementing custom filters, one must ensure that there is no dependency inversion among filters.
Of course, if there are no dependencies between filters, the order of execution is not a concern. Tomcat utilizes org.apache.catalina.core.ApplicationFilterChain to implement the Chain of Responsibility pattern mentioned above. Here, we can understand this concept better with a look at some of the code:
public final class ApplicationFilterChain implements FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
(java.security.PrivilegedExceptionAction<Void>) () -> {
// Perform the actual filter execution
internalDoFilter(req,res);
return null;
}
);
} catch( PrivilegedActionException pe) {
...
}
} else {
// Perform the actual filter execution
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
...
if( Globals.IS_SECURITY_ENABLED ) {
...
} else {
// This should be analyzed together with the Filter class, in fact, this is executing a callback function,
// The third parameter of this method passes the current applicationFilterChain object, combined with the above pos pointer to determine whether the filter chain has been executed completely
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
...
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
...
// Actually perform the servlet service, note that this is just entering into the servlet instance, and not really entering into a specific handle
servlet.service(request, response);
...
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
...
} finally {
...
}
}
}From the code above, we can see that Tomcat uses a pos pointer to keep track of the execution position of filters within the filter chain. Only after all the filters in the chain have been executed and passed, the request and response objects are submitted to the servlet instance for corresponding service processing.
It is important to note that at this point, it does not involve a specific handler, meaning that the filter's processing cannot be refined to requests/responses of a specific handler class but can only more vaguely handle requests/responses at the entire servlet instance level.
Of course, another issue that can be identified from the code above is that there seems to be filtering processing only for resource requests and not for resource responses.
In fact, the filtering processing for resource responses is hidden in each filter’s doFilter method. When implementing a custom filter, we need to follow the logic below to be able to handle resource responses:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// TODO Pre-processing
// Call the doFilter method of the applicationFilterChain object (this is actually a callback logic). It's essential to include this, otherwise, the chain structure will break from this point.
chain.doFilter(request, response);
// TODO Post-processing
}Combining this with the internalDoFilter method in ApplicationFilterChain, it is evident that there is an implied push and pop logic (essentially a method stack). The pre-processing of resource requests is actually a push process, and once all the pre-processing filters are pushed onto the stack, servlet.service(request, response) begins execution.
After the servlet service processing is completed, the pop process begins, where post-processing logic from the last filter (i.e., the position of the last line in the code above) is executed in sequence and exited from the method.
It must be said that this logic is not very friendly to beginners. Since Filter is only an interface and cannot provide a template method like an abstract class, beginners might find it challenging to use without a good example reference and might have similar questions if they are only looking at the source code.
It is also important to remind everyone that when implementing custom filters, one must follow the template mentioned above; otherwise, the chain process might be broken, or the post-logic might not be implemented.
2. Usage in Spring
Although it mentions Spring, what is actually being discussed here is the method of use in Spring Boot. To implement a custom filter in Spring Boot, one simply needs to add logic for injecting it into the Spring container. Spring Boot provides two methods to accomplish this operation:
- Use the
@Componentannotation on the custom filter; - Use the
@WebFilterannotation on the custom filter, and use the@ServletComponentScanannotation on the startup class;
Here, the second method is more recommended for injecting filters, because Spring offers additional functionality not present in the original Tomcat processing, namely, the ability to match URLs.
By combining the urlPattern field in the @WebFilter annotation, Spring can further refine the granularity of filter processing, making it more flexible for developers to use. In addition, to determine the order of filter injection, we can use the @Order annotation provided by Spring to customize the order of filters.
>> Interceptor: A Native of Spring
1. Basic Concept
After exploring filters, let’s turn our attention back to Interceptors. This time, we find that the concept of an Interceptor is originally from Spring, with its specific interface class being HandlerInterceptor (there is also an asynchronous interceptor interface class, which we will not expand upon here, but interested students can read the source code to learn).
After reviewing the corresponding source code, it's clear that, unlike Filter which only provides a simple doFilter method, HandlerInterceptor explicitly offers three methods related to execution timing:
preHandle: Executes before the corresponding handler is executed for pre-processing;postHandle: Executes after the corresponding handler has finished request processing but before the ModelAndView object is rendered, for post-processing related to the ModelAndView object;afterCompletion: Executes after the ModelAndView object has been rendered and before the response is returned, for post-processing of the results;
Compared to the Filter class, which simply provides a doFilter method, the method definitions in HandlerInterceptor are more precise and user-friendly. Without reading the source code or referring to usage examples, we can roughly guess how to implement a custom interceptor.
Combining the source code from org.springframework.web.servlet.DispatcherServlet#doDispatch, we can draw the following flowchart (the specific code is not posted here, but interested students can read it on their own):

It can be observed that the execution logic of the interceptor is all contained within the servlet instance. Combining this with the above explanation of the filter’s execution process, it is not hard to see that the filter acts like the two cookies of a sandwich biscuit, enclosing the servlet and interceptor in the middle, with the execution timing of the interceptor being after the filter’s pre-processing and before the filter’s post-processing.
Moreover, in the process of reading the source code, we can discover that Spring also uses the chain of responsibility pattern when employing interceptors. It must be said that this pattern is very useful in scenarios where different tasks and logic need to be executed sequentially.
It is important to note that since Spring has clearly defined different methods for different stages of execution when designing interceptors, the actual execution of interceptors does not use the same push and pop method as filters.
2. Usage in Spring
To use an interceptor in Spring Boot, in addition to implementing the HandlerInterceptor interface, it also needs to be explicitly registered in Spring's web configuration, as follows:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/api/*").excludePathPatterns("/api/ok");
}
}From the code above, we can see that Spring also provides custom interceptors with the same path matching functionality as filters. With this feature, custom interceptors can handle handler requests and responses at a finer granularity. (This again overlaps with the functionality of filters, but of course, this is a capability provided internally by Spring.)
>> Common Usage Scenarios
Indeed, at the beginning of the article, we have already introduced some of the functions of both entities. Here, we will briefly summarize again.
From the analysis above, it is not difficult to see that the designers of filters and interceptors created these two tools with the goal of separating the pre-processing of requests and the post-processing of responses from business code, providing them as a general processing logic for developers to extend and implement. From this idea, it is easy to see the shadow of AOP (indeed, great minds think alike).
In actual development scenarios, custom filters or interceptors are often used to complete operations such as:
- User login verification;
- Permission checks;
- Logging interception;
- Data compression/decompression;
- Encryption/decryption;
- …
We will not showcase the coding implementation of each scenario here, but interested students can search and learn about them on their own.
Here is a piece of advice: although the scenarios mentioned above seem numerous, their essence is still about processing request parameters or response results. With this understanding, designing and implementing these scenarios will seem relatively easy.
>> Summary
After careful analysis, we can see that there is no fundamental difference between filters and interceptors. As tools, the capabilities they provide are basically the same.
The only thing to note is the difference in their timing of execution (one operates before and after servlet execution, the other operates within the servlet execution); other aspects do not significantly differ. This means that in actual development and use, there is no need to be overly concerned about choosing between them.
Stackademic
Thank you for reading until the end. Before you go:





