📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
1. Overview
This post describes Filters, Interceptors, and their configuration. Filters and Interceptors can be used on both sides, on the client and the server side.Filters can modify inbound and outbound requests and responses including modification of headers, entity, and other requests/response parameters.
Interceptors are used primarily for modification of entity input and output streams. You can use Interceptors for example to zip and unzip output and input entity streams.
2. Filters
Filters can be used when you want to modify any request or response parameters like headers. For example, you would like to add a response header "X-Powered-By" to each generated response. Instead of adding this header in each resource method you would use a response filter to add this header.There are filters on the server side and the client side.
Server filters:
- ContainerRequestFilter
- ContainerResponseFilter
- ClientRequestFilter
- ClientResponseFilter
2.1 Server Filters
Example: Container response filter
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
public class PoweredByResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
- In the example above the PoweredByResponseFilter always adds a header "X-Powered-By" to the response. The filter must inherit from the ContainerResponseFilter and must be registered as a provider.
- The filter will be executed for every response which is in most cases after the resource method is executed. Response filters are executed even if the resource method is not run, for example when the resource method is not found and 404 "Not found" response code is returned by the Jersey runtime. In this case, the filter will be executed and will process the 404 response.
- The filter() method has two arguments, the container request, and container response. The ContainerRequestContext is accessible only for read-only purposes as the filter is executed already in the response phase. The modifications can be done in the ContainerResponseContext.
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
public class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
final SecurityContext securityContext =
requestContext.getSecurityContext();
if (securityContext == null ||
!securityContext.isUserInRole("privileged")) {
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("User cannot access the resource.")
.build());
}
}
}
Pre-Matching and Post-Matching Filters
...
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
...
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
// change all PUT methods to POST
if (requestContext.getMethod().equals("PUT")) {
requestContext.setMethod("POST");
}
}
}
2.2 Client Filters
The process is shown in the following example:
public class CheckRequestFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext requestContext)
throws IOException {
if (requestContext.getHeaders(
).get("Client-Name") == null) {
requestContext.abortWith(
Response.status(Response.Status.BAD_REQUEST)
.entity("Client-Name header must be defined.")
.build());
}
}
}
3. Interceptors
- ReaderInterceptor
- WriterInterceptor
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
Let's now look at an example of a ReaderInterceptor
Example: GZIP reader interceptor
public class GZIPReaderInterceptor implements ReaderInterceptor {
@Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
final InputStream originalInputStream = context.getInputStream();
context.setInputStream(new GZIPInputStream(originalInputStream));
return context.proceed();
}
}
4. Filter and Interceptor Execution Order
- Client request invoked: The POST request with an attached entity is built on the client and invoked.
- ClientRequestFilters: client request filters are executed on the client and they manipulate the request headers.
- Client WriterInterceptor: As the request contains an entity, writer interceptor registered on the client is executed before a MessageBodyWriter is executed. It wraps the entity output stream with the GZipOutputStream.
- Client MessageBodyWriter: message body writer is executed on the client which serializes the entity into the new GZipOutput stream. This stream zips the data and sends it to the "wire".
- Server: server receives a request. Data of entity is compressed which means that pure read from the entity input stream would return compressed data.
- Server pre-matching ContainerRequestFilters: ContainerRequestFilters are executed that can manipulate resource method matching process.
- Server: matching: resource method matching is done.
- Server: post-matching ContainerRequestFilters: ContainerRequestFilters post matching filters are executed. This includes execution of all global filters (without name binding) and filters name-bound to the matched method.
- Server ReaderInterceptor: reader interceptors are executed on the server. The GZIPReaderInterceptor wraps the input stream (the stream from the "wire") into the GZipInputStream and set it to context.
- Server MessageBodyReader: server message body reader is executed and it deserializes the entity from new GZipInputStream (get from the context). This means the reader will read unzipped data and not the compressed data from the "wire".
- Server resource method is executed: the deserialized entity object is passed to the matched resource method as a parameter. The method returns this entity as a response entity.
- Server ContainerResponseFilters are executed: response filters are executed on the server and they manipulate the response headers. This include all global bound filters (without name binding) and all filters name-bound to the resource method.
- Server WriterInterceptor: is executed on the server. It wraps the original output stream with a new GZIPOuptutStream. The original stream is the stream that "goes to the wire" (output stream for response from the underlying server container).
- Server MessageBodyWriter: message body writer is executed on the server which serializes the entity into the GZIPOutputStream. This stream compresses the data and writes it to the original stream which sends this compressed data back to the client.
- Client receives the response: the response contains compressed entity data.
- Client ClientResponseFilters: client response filters are executed and they manipulate the response headers.
- Client response is returned: the javax.ws.rs.core.Response is returned from the request invocation.
- Client code calls response.readEntity(): read entity is executed on the client to extract the entity from the response.
- Client ReaderInterceptor: the client reader interceptor is executed when readEntity(Class) is called. The interceptor wraps the entity input stream with GZIPInputStream. This will decompress the data from the original input stream.
- Client MessageBodyReaders: client message body reader is invoked which reads decompressed data from GZIPInputStream and deserializes the entity.
- Client: The entity is returned from the readEntity().
...
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.zip.GZIPInputStream;
import javax.ws.rs.GET;
import javax.ws.rs.NameBinding;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
...
// @Compress annotation is the name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
@Path("helloworld")
public class HelloWorldResource {
@GET
@Produces("text/plain")
public String getHello() {
return "Hello World!";
}
@GET
@Path("too-much-data")
@Compress
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
// interceptor will be executed only when resource methods
// annotated with @Compress annotation will be executed
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
6. Dynamic binding
...
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.container.DynamicFeature;
...
@Path("helloworld")
public class HelloWorldResource {
@GET
@Produces("text/plain")
public String getHello() {
return "Hello World!";
}
@GET
@Path("too-much-data")
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
// This dynamic binding provider registers GZIPWriterInterceptor
// only for HelloWorldResource and methods that contain
// "VeryLongString" in their name. It will be executed during
// application initialization phase.
public class CompressionDynamicBinding implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())
&& resourceInfo.getResourceMethod()
.getName().contains("VeryLongString")) {
context.register(GZIPWriterInterceptor.class);
}
}
}
7. Priorities
...
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
...
@Priority(Priorities.HEADER_DECORATOR)
public class ResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
Comments
Post a Comment
Leave Comment