Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 5

Implementing Servlet Filters

Recommended resources

Hello! Welcome to your fifth lesson on Java backend development.

In our last lesson, we explored the servlet lifecycle. You learned how the servlet container manages a single servlet instance, calling init() once for setup, service() for every request, and destroy() once for cleanup. This gave you a clear picture of how a servlet operates "under the hood."

Today, we'll address a new question: What if you need to execute code before a request reaches a servlet, or after the servlet has finished but before the response is sent to the client? This is a common requirement for tasks like logging, authentication, or data compression.

This lesson covers the learning outcome: Implement a Servlet Filter to intercept and process requests. We will learn about Servlet Filters, which are powerful, pluggable components designed for this exact purpose. Understanding filters is crucial because they are a cornerstone of how modern frameworks like Spring handle cross-cutting concerns.

What is a Servlet Filter?

A Servlet Filter is an object that can intercept and process HTTP requests before they reach a servlet and can modify the response after the servlet has done its work. You can think of it like a series of checkpoints or security gates that a request must pass through on its way to its final destination (the servlet).

Filters are an implementation of the Intercepting Filter design pattern. They help you centralize common processing steps that apply to multiple resources, avoiding code duplication and making your application easier to maintain.

Common use cases for filters include:

  • Authentication: Checking if a user is logged in before allowing access to protected pages.
  • Logging: Recording details about incoming requests for auditing or debugging.
  • Data Compression: Compressing response data to reduce bandwidth usage.
  • Character Encoding: Setting the character encoding for requests and responses to prevent data corruption.

The diagram below perfectly illustrates how filters change the request-response flow.

Servlet Request-Response Flow with and Without Filters
This diagram compares the request flow. On the left, the request goes directly to the servlet. On the right, the request passes through a chain of filters before reaching the servlet, and the response passes back through the same chain in reverse order.

The Filter API and Lifecycle

Just like servlets, filters have a well-defined lifecycle managed by the servlet container. To create a filter, you implement the javax.servlet.Filter interface, which has three key methods.

To understand the core concepts, let's start with a reading from DigitalOcean's tutorial.

Java Servlet Filter Example Tutorial

The article 'Java Servlet Filter Example Tutorial' provides an excellent introduction to why we need filters and the API used to create them.

Please read the sections 'Why do we have Servlet Filter?' and 'Servlet Filter interface'. Focus on understanding the purpose of the three lifecycle methods: init(), doFilter(), and destroy().

As you've just read, the filter lifecycle is very similar to the servlet lifecycle:

  1. init(FilterConfig config): Called once by the container when the filter is instantiated. Use this for one-time setup tasks, like loading resources or reading configuration parameters from FilterConfig.
  2. doFilter(ServletRequest request, ServletResponse response, FilterChain chain): This is the heart of the filter. The container calls this method for every request that maps to the filter. Your interception logic goes here.
  3. destroy(): Called once when the filter is taken out of service. Use this to release any resources acquired in the init() method.

The image below shows the lifecycle stages, which should look familiar after our last lesson on servlets.

Servlet Filter Lifecycle
This flowchart illustrates the key stages in a filter's life: initialization at server startup, processing during each request, and deinstantiation at server shutdown.

The FilterChain

The most critical part of the doFilter method is the FilterChain parameter. This object represents the chain of filters that the request must pass through.

When you call chain.doFilter(request, response), you are telling the container to pass the request and response objects to the next filter in the chain. If there are no more filters, the request is passed to the target servlet.

This mechanism is what allows for both pre-processing and post-processing:

  • Pre-processing: Any code you write before calling chain.doFilter().
  • Post-processing: Any code you write after calling chain.doFilter(). This code will execute after the servlet (and all subsequent filters) have completed.

Implementing and Configuring Filters

Now, let's put theory into practice. We'll build two common filters: one for logging and one for authentication. The same article we used before provides a complete, working example.

Java Servlet Filter Example Tutorial

This part of the 'Java Servlet Filter Example Tutorial' provides the full source code for an application that uses two filters for logging and authentication. It's a great practical example.

Read the section 'Servlet Filter Example for Logging and session validation'. You don't need to copy and run all the code right now. Instead, focus on understanding the implementation of the two filter classes: RequestLoggingFilter and AuthenticationFilter, and how they are configured in web.xml.

Let's break down the key parts of that example.

Example 1: RequestLoggingFilter

This filter demonstrates simple pre-processing. Its goal is to log information about the request before passing it on.

// A simplified version based on the example
public class RequestLoggingFilter implements Filter {

    private ServletContext context;

    public void init(FilterConfig fConfig) throws ServletException {
        this.context = fConfig.getServletContext();
        this.context.log("RequestLoggingFilter initialized");
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest) request;
        
        // Pre-processing: Log the remote address
        this.context.log("Request from " + req.getRemoteAddr());

        // Pass the request along the filter chain
        chain.doFilter(request, response);
        
        // Post-processing could go here, e.g., logging response status
    }

    public void destroy() {
        this.context.log("RequestLoggingFilter destroyed");
    }
}

Notice how the code logs the address and then immediately calls chain.doFilter().

Example 2: AuthenticationFilter

This filter is more complex. It intercepts the request and decides whether to continue the chain or redirect the user to a login page.

// A simplified version based on the example
public class AuthenticationFilter implements Filter {
    
    private ServletContext context;

    public void init(FilterConfig fConfig) throws ServletException {
        this.context = fConfig.getServletContext();
        this.context.log("AuthenticationFilter initialized");
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        String uri = req.getRequestURI();
        this.context.log("Requested Resource::" + uri);

        HttpSession session = req.getSession(false); // false = do not create new session

        // Check if user is logged in (session exists and has a 'user' attribute)
        // Also, don't block access to the login page itself!
        if (session == null && !(uri.endsWith("login.html") || uri.endsWith("LoginServlet"))) {
            this.context.log("Unauthorized access request");
            res.sendRedirect("login.html"); // Redirect to login page
        } else {
            // User is authenticated or is accessing a public page, so continue.
            chain.doFilter(request, response);
        }
    }

    // ... destroy() method
}

This is a powerful pattern. By not calling chain.doFilter(), the filter effectively stops the request from ever reaching the servlet.

Configuration Methods

As you saw in the resources, there are two ways to register a filter and map it to URLs:

  1. web.xml Deployment Descriptor:

    • You define the filter with a <filter> tag.
    • You map it to a URL pattern or servlet name with a <filter-mapping> tag.
    • The order of <filter-mapping> elements in web.xml determines the execution order of the filters.
    <!-- Define the filters -->
    <filter>
        <filter-name>RequestLoggingFilter</filter-name>
        <filter-class>com.journaldev.servlet.filters.RequestLoggingFilter</filter-class>
    </filter>
    <filter>
        <filter-name>AuthenticationFilter</filter-name>
        <filter-class>com.journaldev.servlet.filters.AuthenticationFilter</filter-class>
    </filter>
    
    <!-- Map the filters to URLs (order matters here!) -->
    <filter-mapping>
        <filter-name>RequestLoggingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>AuthenticationFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  2. @WebFilter Annotation (Servlet 3.0+):

    • This is a more modern, convenient approach. You add the annotation directly to your filter class.
    • You can specify the URL patterns directly in the annotation.
    • Note: The execution order is not guaranteed when using annotations alone. For strict ordering, web.xml is more reliable.
    @WebFilter(urlPatterns = "/*")
    public class RequestLoggingFilter implements Filter {
        // ...
    }
    
Test your understanding!

Using the web.xml configuration above, the RequestLoggingFilter runs before the AuthenticationFilter.

  1. An unauthenticated user tries to access /profile.jsp. What happens?
  2. If you swapped the order of the <filter-mapping> blocks, would the log message from RequestLoggingFilter still appear for the unauthenticated request to /profile.jsp?
Show answer
  1. The request first hits RequestLoggingFilter, which logs the request. Then it hits AuthenticationFilter, which finds no session and redirects the user to login.html. The request never reaches /profile.jsp.
  2. If you swapped the order, the request would first hit AuthenticationFilter. It would find no session and immediately issue a redirect. The RequestLoggingFilter would not be executed for this request, so nothing would be logged. This demonstrates why filter order is critical.

Key Takeaways

  • Servlet Filters intercept requests and responses, allowing you to implement cross-cutting concerns like logging, authentication, and data transformation.
  • A filter is a Java class that implements the javax.servlet.Filter interface and its init(), doFilter(), and destroy() methods.
  • The FilterChain is used to pass control to the next filter or to the servlet. Calling chain.doFilter() is essential for the request to proceed.
  • You can stop the request from reaching the servlet by simply not calling chain.doFilter().
  • Filters are configured either in the web.xml deployment descriptor or with the @WebFilter annotation. web.xml gives you explicit control over the filter execution order.

What's Next?

We've now covered the fundamental building blocks of a Java web application: servlets that handle requests and filters that intercept them. This ability to centralize request processing is a powerful concept.

In our next lesson, we will explore the Model-View-Controller (MVC) design pattern. You'll see how this pattern provides a high-level structure for organizing your entire web application and how filters often play a key role in the "Controller" part of MVC. This will take us one step closer to understanding the architecture of frameworks like Spring.