Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Filters

Some actions are needed for every request: log it, add a header, check access. Copying that code into every servlet is inconvenient. A filter is a class that the request passes through before the servlet and the response passes through after it. Filters work for servlets, REST resources and static files alike.

A logging filter

Create the file src/main/java/com/example/blog/LoggingFilter.java:

// src/main/java/com/example/blog/LoggingFilter.java
package com.example.blog;
 
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@WebFilter("/*")
public class LoggingFilter extends HttpFilter {
 
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response,
                            FilterChain chain) throws IOException, ServletException {
        long start = System.currentTimeMillis();
 
        chain.doFilter(request, response);
 
        long time = System.currentTimeMillis() - start;
        System.out.println(request.getMethod() + " " + request.getRequestURI()
                + " -> " + response.getStatus() + " (" + time + " ms)");
    }
}

@WebFilter("/*") enables the filter for every address of the application. The chain.doFilter call passes the request on — to the next filter or to the servlet. Code before that line runs before handling, code after it runs when the response is ready.

Make a few requests, and the liberty:dev terminal (and the server's console.log) shows lines like these:

GET /blog/ -> 200 (27 ms)
GET /blog/search -> 200 (4 ms)
GET /blog/search -> 400 (4 ms)
POST /blog/subscribe -> 302 (5 ms)

getRequestURI() does not include the query string, so ?q=java is not in the log.

A filter that changes the response

The second filter adds a header to every response:

// src/main/java/com/example/blog/PoweredByFilter.java
package com.example.blog;
 
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@WebFilter("/*")
public class PoweredByFilter extends HttpFilter {
 
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response,
                            FilterChain chain) throws IOException, ServletException {
        response.setHeader("X-Powered-By", "Blog on Jakarta EE");
        chain.doFilter(request, response);
    }
}

The header is set before chain.doFilter: once the servlet starts writing the body, it is too late to change headers.

curl -i http://localhost:8080/blog/hello
HTTP/1.1 200 OK
X-Powered-By: Blog on Jakarta EE
Content-Type: text/plain; charset=UTF-8
Content-Length: 22

Hello from the blog!

What filters usually do

TaskExample
LoggingMethod, address, response time
HeadersCORS, caching, security
Access checksNo session — redirect to the login page
EncodingSet UTF-8 for all requests

URL patterns are the same as for servlets: /*, /admin/*, *.html. The order of several annotated filters is not guaranteed. If it matters, list the filters in src/main/webapp/WEB-INF/web.xml.

Common mistake

Forgetting to call chain.doFilter(request, response). Then the request never reaches the servlet, and the client gets an empty response with status 200:

HTTP/1.1 200 OK
X-Powered-By: Blog on Jakarta EE
Content-Length: 0

Skip chain.doFilter only on purpose — for example, when the filter itself answers with a 403 error.

Thanks for reading!

Donate to the site — we work hard for you.