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/helloHTTP/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
| Task | Example |
|---|---|
| Logging | Method, address, response time |
| Headers | CORS, caching, security |
| Access checks | No session — redirect to the login page |
| Encoding | Set 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.