How chain.doFilter() Works in a Java Spring Filter
When working with filters in Spring or Java/Jakarta EE, we might encounter the doFilter()
method in the Filter
interface. This method often includes a call to chain.doFilter()
. At first glance, it might seem like a recursive call, but it’s not. In this article, we’ll explore the role of chain.doFilter()
in the context of the Filter interface, how it works, and its importance in the Chain of Responsibility design pattern.
Filters are components used in web applications to process HTTP requests and responses before they reach the target servlet or controller. They are chained together, forming a sequence. The chain.doFilter()
method is crucial because it hands over control to the next filter in the chain, and eventually to the target resource (e.g., a servlet or Spring controller).
1. Chain of Responsibility Design Pattern in Filters
Filters are an implementation of the Chain of Responsibility design pattern. Each filter in the chain performs its processing and decides whether to pass the request and response to the next filter by calling chain.doFilter()
. If chain.doFilter()
is not called, the request processing stops at that point.
1.1 Illustrative Example: Understanding chain.doFilter()
Below is an example of a Spring Filter
implementation that demonstrates the use of chain.doFilter()
.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | @Component public class LoggingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println( "Before processing request in Logging Filter" ); // Pass the request and response to the next filter or target servlet chain.doFilter(request, response); System.out.println( "After processing request in Logging Filter" ); } } |
This example demonstrates a simple logging filter that intercepts requests, logs the processing before and after invoking chain.doFilter()
, and then passes control to the next filter or the target resource.
The doFilter()
method is executed for every incoming request. Before invoking chain.doFilter()
, a log message is recorded to indicate the start of processing within the filter. The chain.doFilter()
call then passes control to the next filter in the chain or the target servlet if this is the final filter. Once chain.doFilter()
completes, another log message is recorded to indicate that control has returned to this filter.
Custom Endpoint Example
If your application has a specific REST controller or servlet, an endpoint like /api/hello
could be used for testing. Below is an example controller named HelloController
.
1 2 3 4 5 6 7 8 | @RestController public class HelloController { @GetMapping ( "/api/hello" ) public String sayHello() { return "Hello, world!" ; } } |
With the above controller, we can test the filters by making an HTTP GET request to the following endpoint: http://localhost:8080/api/hello
Observing Filter Logs
When you make the HTTP request, check your server logs. The filters will log messages as they process the request and response. This demonstrates how chain.doFilter()
works as the request flows through the filter chain. In the above example, when you make an HTTP request to the server, the following logs will appear in the console:
2. How Filters Are Chained Together
In Spring, filters are processed in a chain according to the Chain of Responsibility design pattern. The order of execution is determined by the @Order
annotation. Filters with a lower order value are executed first. Now let’s introduce another filter to the chain. This filter validates authentication before passing the request to the next filter.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | @Component public class AuthenticationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println( "Authentication Filter: Validating authentication" ); // Passing the request to the next filter or servlet chain.doFilter(request, response); System.out.println( "Authentication Filter: Post-processing response" ); } } |
Below is the update of the LoggingFilter
and AuthenticationFilter
classes with the @Order
annotation introduced.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | @Order ( 1 ) @Component public class LoggingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println( "Before processing request in Logging Filter" ); // Pass the request and response to the next filter or target servlet chain.doFilter(request, response); System.out.println( "After processing request in Logging Filter" ); } } @Order ( 2 ) @Component public class AuthenticationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println( "Authentication Filter: Validating authentication" ); // Passing the request to the next filter or servlet chain.doFilter(request, response); System.out.println( "Authentication Filter: Post-processing response" ); } } |
2.1 How These Filters Are Chained Together
- Order Annotation:
- The
@Order(1)
annotation on theLoggingFilter
ensures that it is the first filter to execute in the chain. - The
@Order(2)
annotation on theAuthenticationFilter
ensures that it is the second filter to execute, after theLoggingFilter
.
- The
- Filter Chain Execution:
- The
doFilter()
method in each filter passes the request and response to the next filter in the chain usingchain.doFilter(request, response)
. - The chain continues until the last filter or target resource (e.g., a servlet or controller) is reached.
- Once the downstream processing completes, control flows back through the filters in reverse order.
- The
Output of Combined Filters
After we configure both LoggingFilter
and AuthenticationFilter
, the output will look like this:
1 2 3 4 | Before processing request in Logging Filter Authentication Filter: Validating authentication Authentication Filter: Post-processing response After processing request in Logging Filter |
2.2 Output Explanation
When a request is made, the filters execute as follows:
LoggingFilter
(First Filter):- Logs “Before processing request in Logging Filter.”
- Passes the request to the next filter (via
chain.doFilter()
).
AuthenticationFilter
(Second Filter):- Logs “Authentication Filter: Validating authentication.”
- Passes the request further down the chain (via
chain.doFilter()
), possibly to the servlet or controller.
- Servlet/Controller:
- Handles the request and generates a response.
AuthenticationFilter
(Returning Response):- Logs “Authentication Filter: Post-processing response” as the response flows back through the chain.
LoggingFilter
(Returning Response):- Logs “After processing request in Logging Filter” as the response flows back to the client.
3. The No Annotation Approach
In Spring Boot, filters can be automatically registered using the @Component
and @Order
annotations. Alternatively, we can use a FilterRegistrationBean
to register them and define the sequence explicitly. Here is an example:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<LoggingFilter> loggingFilter() { FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter( new LoggingFilter()); registrationBean.setOrder( 1 ); // LoggingFilter executes first return registrationBean; } @Bean public FilterRegistrationBean<AuthenticationFilter> authenticationFilter() { FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter( new AuthenticationFilter()); registrationBean.setOrder( 2 ); // AuthenticationFilter executes second return registrationBean; } } |
4. Conclusion
In this article, we explored the role of chain.doFilter()
in Java Spring filters, demonstrating its importance in the Chain of Responsibility design pattern. Through detailed examples and logging outputs, we saw how filters like LoggingFilter
and AuthenticationFilter
process HTTP requests and responses in a sequential manner. By leveraging chain.doFilter()
, each filter ensures that control is passed to the next filter or the target servlet, enabling modular and reusable request processing logic. By understanding chain.doFilter()
, we can create reliable filters that efficiently manage requests and responses in our web application.
5. Download the Source Code
This article explored Java Spring’s chain.doFilter method.
You can download the full source code of this example here: java spring chain dofilter