BLOG POSTS
Java Servlet Filter Example Tutorial

Java Servlet Filter Example Tutorial

Java Servlet Filters represent one of the most powerful yet underutilized features in web application development, acting as interceptors that can modify requests and responses before they reach your servlets or after they leave. These components enable cross-cutting concerns like authentication, logging, data compression, and security enforcement without cluttering your core business logic. In this tutorial, you’ll learn how to create, configure, and deploy servlet filters effectively, understand their lifecycle, explore practical implementation patterns, and avoid common pitfalls that can impact performance and functionality.

How Servlet Filters Work

Servlet filters operate on the interceptor pattern, forming a chain of processing components between the client request and your servlet. When a request arrives, it passes through each filter in the chain before reaching the target servlet. Similarly, the response travels back through the same filters in reverse order.

The filter chain mechanism allows multiple filters to process the same request, with each filter having the opportunity to:

  • Examine and modify request headers, parameters, and content
  • Block requests that don’t meet specific criteria
  • Add or modify response headers and content
  • Log request and response information
  • Measure processing time and performance metrics

Every filter must implement the javax.servlet.Filter interface, which defines three essential methods:

public interface Filter {
    void init(FilterConfig config) throws ServletException;
    void doFilter(ServletRequest request, ServletResponse response, 
                  FilterChain chain) throws IOException, ServletException;
    void destroy();
}

Step-by-Step Filter Implementation Guide

Let’s start with a basic logging filter that demonstrates the fundamental concepts. This filter will log incoming requests and measure response times.

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Logger;

public class RequestLoggingFilter implements Filter {
    private static final Logger logger = Logger.getLogger(RequestLoggingFilter.class.getName());
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        logger.info("RequestLoggingFilter initialized");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        long startTime = System.currentTimeMillis();
        String requestURI = httpRequest.getRequestURI();
        String method = httpRequest.getMethod();
        String clientIP = httpRequest.getRemoteAddr();
        
        logger.info(String.format("Incoming request: %s %s from %s", 
                                 method, requestURI, clientIP));
        
        try {
            // Continue the filter chain
            chain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            int status = httpResponse.getStatus();
            logger.info(String.format("Request completed: %s %s - Status: %d - Duration: %dms", 
                                     method, requestURI, status, duration));
        }
    }

    @Override
    public void destroy() {
        logger.info("RequestLoggingFilter destroyed");
        filterConfig = null;
    }
}

Next, configure the filter in your web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <filter>
        <filter-name>RequestLoggingFilter</filter-name>
        <filter-class>com.example.RequestLoggingFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>RequestLoggingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

For applications using Servlet 3.0+ annotations, you can simplify configuration:

import javax.servlet.annotation.WebFilter;

@WebFilter(urlPatterns = {"/*"}, filterName = "RequestLoggingFilter")
public class RequestLoggingFilter implements Filter {
    // Implementation remains the same
}

Real-World Examples and Use Cases

Let’s explore several practical filter implementations that address common web application requirements.

Authentication Filter

This filter ensures users are authenticated before accessing protected resources:

@WebFilter(urlPatterns = {"/admin/*", "/user/*"})
public class AuthenticationFilter implements Filter {
    
    private Set<String> publicPaths;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        publicPaths = new HashSet<>();
        publicPaths.add("/login");
        publicPaths.add("/register");
        publicPaths.add("/public");
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpSession session = httpRequest.getSession(false);
        
        String requestURI = httpRequest.getRequestURI();
        String contextPath = httpRequest.getContextPath();
        String path = requestURI.substring(contextPath.length());
        
        boolean isAuthenticated = (session != null && session.getAttribute("user") != null);
        boolean isPublicPath = publicPaths.stream().anyMatch(path::startsWith);
        
        if (isAuthenticated || isPublicPath) {
            chain.doFilter(request, response);
        } else {
            httpResponse.sendRedirect(contextPath + "/login");
        }
    }
    
    @Override
    public void destroy() {
        publicPaths = null;
    }
}

CORS Filter

Essential for modern web applications that serve APIs to different domains:

@WebFilter(urlPatterns = {"/api/*"})
public class CORSFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // Set CORS headers
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");
        httpResponse.setHeader("Access-Control-Allow-Headers", 
                              "Content-Type, Authorization, X-Requested-With");
        
        // Handle preflight requests
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        
        chain.doFilter(request, response);
    }
}

Response Compression Filter

Reduces bandwidth usage by compressing response content:

@WebFilter(urlPatterns = {"*.html", "*.css", "*.js", "/api/*"})
public class CompressionFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
        
        if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
            GzipResponseWrapper wrappedResponse = new GzipResponseWrapper(httpResponse);
            chain.doFilter(request, wrappedResponse);
            wrappedResponse.finishResponse();
        } else {
            chain.doFilter(request, response);
        }
    }
}

Filter Configuration and Ordering

Filter execution order matters significantly. Filters declared first in web.xml execute first in the request chain and last in the response chain. Here’s how different configuration methods handle ordering:

Configuration Method Ordering Control Flexibility Best Use Case
web.xml Declaration order High Complex applications with many filters
@WebFilter Alphabetical by class name Low Simple applications with few filters
ServletContext.addFilter() Registration order High Dynamic filter registration

For programmatic filter registration, use a ServletContextListener:

@WebListener
public class FilterRegistrationListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        
        // Register filters in specific order
        FilterRegistration.Dynamic corsFilter = 
            context.addFilter("CORSFilter", new CORSFilter());
        corsFilter.addMappingForUrlPatterns(null, false, "/api/*");
        
        FilterRegistration.Dynamic authFilter = 
            context.addFilter("AuthFilter", new AuthenticationFilter());
        authFilter.addMappingForUrlPatterns(null, false, "/admin/*");
        
        FilterRegistration.Dynamic loggingFilter = 
            context.addFilter("LoggingFilter", new RequestLoggingFilter());
        loggingFilter.addMappingForUrlPatterns(null, false, "/*");
    }
}

Performance Considerations and Best Practices

Filter performance directly impacts your application’s response times. Here are key optimization strategies:

  • Minimize filter scope: Apply filters only to URLs that actually need them rather than using /* for everything
  • Fail fast: Perform expensive operations only when necessary, and exit early when possible
  • Cache expensive computations: Store results of costly operations in request attributes or session
  • Avoid blocking operations: Don’t perform database queries or external API calls unless absolutely necessary
  • Use appropriate data structures: HashSet for lookups, ArrayList for iteration

Here’s a performance-optimized authentication filter:

@WebFilter(urlPatterns = {"/admin/*"})
public class OptimizedAuthFilter implements Filter {
    
    private static final Set<String> PUBLIC_PATHS = Collections.unmodifiableSet(
        new HashSet<>(Arrays.asList("/login", "/register", "/public"))
    );
    
    private static final String USER_ATTR = "authenticated_user";
    private static final String AUTH_CHECK_ATTR = "auth_checked";
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // Check if authentication was already verified in this request
        if (httpRequest.getAttribute(AUTH_CHECK_ATTR) != null) {
            chain.doFilter(request, response);
            return;
        }
        
        String path = getRelativePath(httpRequest);
        
        // Quick exit for public paths
        if (isPublicPath(path)) {
            httpRequest.setAttribute(AUTH_CHECK_ATTR, Boolean.TRUE);
            chain.doFilter(request, response);
            return;
        }
        
        // Check authentication
        HttpSession session = httpRequest.getSession(false);
        boolean isAuthenticated = (session != null && session.getAttribute(USER_ATTR) != null);
        
        httpRequest.setAttribute(AUTH_CHECK_ATTR, Boolean.TRUE);
        
        if (isAuthenticated) {
            chain.doFilter(request, response);
        } else {
            httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
        }
    }
    
    private String getRelativePath(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        return requestURI.substring(contextPath.length());
    }
    
    private boolean isPublicPath(String path) {
        return PUBLIC_PATHS.stream().anyMatch(path::startsWith);
    }
}

Common Pitfalls and Troubleshooting

Several issues frequently occur when implementing servlet filters. Here are the most common problems and their solutions:

Forgetting to Call chain.doFilter()

This is the most common mistake that breaks the entire filter chain:

// WRONG - Missing chain.doFilter() call
public void doFilter(ServletRequest request, ServletResponse response, 
                    FilterChain chain) throws IOException, ServletException {
    // Some processing
    if (someCondition) {
        response.getWriter().write("Access denied");
        return; // This stops the chain completely
    }
    // Missing: chain.doFilter(request, response);
}

// CORRECT - Always call chain.doFilter() when request should continue
public void doFilter(ServletRequest request, ServletResponse response, 
                    FilterChain chain) throws IOException, ServletException {
    if (someCondition) {
        response.getWriter().write("Access denied");
        return;
    }
    chain.doFilter(request, response); // Essential for proper flow
}

Response Already Committed Errors

Attempting to modify response headers or redirect after the response has been written:

// WRONG - Trying to modify response after chain execution
public void doFilter(ServletRequest request, ServletResponse response, 
                    FilterChain chain) throws IOException, ServletException {
    chain.doFilter(request, response);
    
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    httpResponse.setHeader("X-Custom-Header", "value"); // May fail if response committed
}

// CORRECT - Modify response before chain execution
public void doFilter(ServletRequest request, ServletResponse response, 
                    FilterChain chain) throws IOException, ServletException {
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    
    if (!httpResponse.isCommitted()) {
        httpResponse.setHeader("X-Custom-Header", "value");
    }
    
    chain.doFilter(request, response);
}

Filter Mapping Issues

Incorrect URL patterns can cause filters to not execute or execute too broadly:

<!-- Common mapping patterns and their effects -->
<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <url-pattern>/admin</url-pattern>     <!-- Only matches exact /admin -->
</filter-mapping>

<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>   <!-- Matches /admin/ and subdirectories -->
</filter-mapping>

<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>      <!-- Matches all JSP files -->
</filter-mapping>

Advanced Filter Patterns

For complex applications, consider these advanced implementation patterns:

Conditional Filter Chain

Skip certain filters based on request characteristics:

@WebFilter(urlPatterns = {"/*"})
public class ConditionalFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // Skip processing for static resources
        String requestURI = httpRequest.getRequestURI();
        if (requestURI.matches(".*\\.(css|js|png|jpg|gif|ico)$")) {
            chain.doFilter(request, response);
            return;
        }
        
        // Skip for health checks
        if ("/health".equals(requestURI) || "/status".equals(requestURI)) {
            chain.doFilter(request, response);
            return;
        }
        
        // Apply full processing for other requests
        processRequest(httpRequest);
        chain.doFilter(request, response);
    }
    
    private void processRequest(HttpServletRequest request) {
        // Complex processing logic here
    }
}

Request/Response Wrapper Pattern

Modify request or response content by wrapping them:

public class RequestModificationFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        // Wrap request to modify parameters
        HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper((HttpServletRequest) request) {
            @Override
            public String getParameter(String name) {
                String value = super.getParameter(name);
                if (value != null && "userInput".equals(name)) {
                    // Sanitize input
                    return value.replaceAll("[<>\"']", "");
                }
                return value;
            }
        };
        
        chain.doFilter(requestWrapper, response);
    }
}

Testing Servlet Filters

Testing filters requires proper setup of mock objects and filter chains:

import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;

public class RequestLoggingFilterTest {
    
    @Mock
    private HttpServletRequest request;
    
    @Mock
    private HttpServletResponse response;
    
    @Mock
    private FilterChain filterChain;
    
    private RequestLoggingFilter filter;
    
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        filter = new RequestLoggingFilter();
    }
    
    @Test
    public void testFilterLogsRequest() throws Exception {
        // Setup
        when(request.getRequestURI()).thenReturn("/test");
        when(request.getMethod()).thenReturn("GET");
        when(request.getRemoteAddr()).thenReturn("127.0.0.1");
        when(response.getStatus()).thenReturn(200);
        
        // Execute
        filter.doFilter(request, response, filterChain);
        
        // Verify
        verify(filterChain).doFilter(request, response);
        verify(request).getRequestURI();
        verify(request).getMethod();
        verify(request).getRemoteAddr();
    }
}

When deploying applications with servlet filters, proper server configuration becomes crucial. Whether you’re using a VPS for development and testing or dedicated servers for production environments, ensure your servlet container is properly tuned for filter performance. Monitor filter execution times and consider implementing filter-specific metrics to identify bottlenecks in your application’s request processing pipeline.

Servlet filters provide a clean, maintainable way to implement cross-cutting concerns in Java web applications. By following these patterns and best practices, you’ll create robust, performant filters that enhance your application’s functionality without compromising code quality. Remember to keep filters focused on single responsibilities, test them thoroughly, and monitor their performance impact in production environments.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked