Enterprise Java

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:

Screenshot showing the log output of the Java Spring chain.doFilter LoggingFilter example.

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

  1. Order Annotation:
    • The @Order(1) annotation on the LoggingFilter ensures that it is the first filter to execute in the chain.
    • The @Order(2) annotation on the AuthenticationFilter ensures that it is the second filter to execute, after the LoggingFilter.
  2. Filter Chain Execution:
    • The doFilter() method in each filter passes the request and response to the next filter in the chain using chain.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.

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:

  1. LoggingFilter (First Filter):
    • Logs “Before processing request in Logging Filter.”
    • Passes the request to the next filter (via chain.doFilter()).
  2. AuthenticationFilter (Second Filter):
    • Logs “Authentication Filter: Validating authentication.”
    • Passes the request further down the chain (via chain.doFilter()), possibly to the servlet or controller.
  3. Servlet/Controller:
    • Handles the request and generates a response.
  4. AuthenticationFilter (Returning Response):
    • Logs “Authentication Filter: Post-processing response” as the response flows back through the chain.
  5. 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.

Download
You can download the full source code of this example here: java spring chain dofilter

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button