How to compress responses in Java REST API with GZip and Jersey

(P) Codever is an open source bookmarks and snippets manager for developers & co. See our How To guides to help you get started. Public bookmarks repos on Github ⭐🙏
There may be cases when your REST api provides responses that are very long, and we all know how important transfer speed and bandwidth still are on mobile devices/networks. I think this is the first performance optimization point one needs to address, when developing REST apis that support mobile apps. Guess what? Because responses are text, we can compress them. And with today’s power of smartphones and tablets uncompressing them on the client side should not be a big deal… So in this post I will present how you can SELECTIVELY compress your REST API responses, if you’ve built it in Java with Jersey, which is the JAX-RS Reference Implementation (and more)…
Short Version (compress every response)
On the server side, you can easily compress every response with Jersey’s EncodingFilter
, which is a container filter that supports enconding-based content configuration. The filter examines what content encodings are supported by the container and decides what encoding should be chosen based on the encodings listed in the Accept-Encoding
request header and their associated quality values. To use particular content en/decoding, you need to register one or more content encoding providers that provide specific encoding support; currently there are providers for GZIP and DEFLATE coding.
All I had to do in this case is adding the following line of code to my JaxRs application class:
public class RestDemoJaxRsApplication extends ResourceConfig {
/**
* Register JAX-RS application components.
*/
public RestDemoJaxRsApplication() {
packages("org.codingpedia.demo.rest");
register(EntityFilteringFeature.class);
EncodingFilter.enableFor(this, GZipEncoder.class);
}
}
Thank you Marek Potociar(@marek_potociar) for pointing this out.
The only advantage I see now for my former solution presented below is having the possibility to compress responses more granularly, on a method(resource) level (but why would you do that?). Read on if you also want to learn how to use WriterInterceptors
with annotations.
Longer version – selectively compress responses
1. Jersey filters and interceptors
Well, thanks to Jersey’s powerful Filters and Interceptors features, the implementation is fairly easy. Whereas filters are primarily intended to manipulate request and response parameters like HTTP headers, URIs and/or HTTP methods, interceptors are intended to manipulate entities, via manipulating entity input/output streams.
You’ve seen the power of filters in my posts
-
How to add CORS support on the server side in Java with Jersey, where I’ve shown how to CORS-enable a REST API
and
-
How to log in Spring with SLF4J and Logback, where I’ve shown how to log requests and responses from the REST API
, but for compressing will be using a GZip WriterInterceptor
. A writer interceptor is used for cases where entity is written to the “wire”, which on the server side as in this case, means when writing out a response entity.
1.1. GZip Writer Interceptor
So let’s have a look at our GZip Writer Interceptor:
package org.codingpedia.demo.rest.interceptors;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
@Provider
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
MultivaluedMap<String,Object> headers = context.getHeaders();
headers.add("Content-Encoding", "gzip");
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
Note:
WriterInterceptor
, which is an interface for message body writer interceptors that wrap around calls to javax.ws.rs.ext.MessageBodyWriter.writeTo
WriterInterceptor
contract must be either programmatically registered in a JAX-RS runtime or must be annotated with @Provider annotation to be automatically discovered by the JAX-RS runtime during a provider scanning phase.
@Compress
is the name binding annotation, which we will discuss more detailed in the coming paragraph
1.2. Compress annotation
Filters and interceptors can be name-bound. Name binding is a concept that allows to say to a JAX-RS runtime that a specific filter or interceptor will be executed only for a specific resource method. When a filter or an interceptor is limited only to a specific resource method we say that it is name-bound. Filters and interceptors that do not have such a limitation are called global. In our case we’ve built the @Compress annotation:
package org.codingpedia.demo.rest.interceptors;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.ws.rs.NameBinding;
//@Compress annotation is the name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
and used it to mark methods on resources which should be gzipped (e.g. when GET-ing all the podcasts with the PodcastsResource
):
@Component
@Path("/podcasts")
public class PodcastsResource {
@Autowired
private PodcastService podcastService;
...........................
/*
* *********************************** READ ***********************************
*/
/**
* Returns all resources (podcasts) from the database
*
* @return
* @throws IOException
* @throws JsonMappingException
* @throws JsonGenerationException
* @throws AppException
*/
@GET
@Compress
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public List<Podcast> getPodcasts(
@QueryParam("orderByInsertionDate") String orderByInsertionDate,
@QueryParam("numberDaysToLookBack") Integer numberDaysToLookBack)
throws IOException, AppException {
List<Podcast> podcasts = podcastService.getPodcasts(
orderByInsertionDate, numberDaysToLookBack);
return podcasts;
}
...........................
}
2. Testing
2.1. SOAPui
Well, if you are testing with SOAPui, you can issue the following request against the PodcastsResource
Request:
GET http://localhost:8888/demo-rest-jersey-spring/podcasts/?orderByInsertionDate=DESC HTTP/1.1
Accept-Encoding: gzip,deflate
Accept: application/json, application/xml
Host: localhost:8888
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip
Content-Length: 409
Server: Jetty(9.0.7.v20131107)
[
{
"id": 2,
"title": "Quarks & Co - zum Mitnehmen",
"linkOnPodcastpedia": "https://github.com/CodepediaOrg/podcastpedia/quarks",
"feed": "https://podcast.wdr.de/quarks.xml",
"description": "Quarks & Co: Das Wissenschaftsmagazin",
"insertionDate": "2014-10-29T10:46:13.00+0100"
},
{
"id": 1,
"title": "- The Naked Scientists Podcast - Stripping Down Science",
"linkOnPodcastpedia": "https://github.com/CodepediaOrg/podcastpedia/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
"feed": "feed_placeholder",
"description": "The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.",
"insertionDate": "2014-10-29T10:46:02.00+0100"
}
]
SOAPui recognizes the Content-Type: gzip
header, we’ve added in the GZIPWriterInterceptor
and automatically uncompresses the response and displays it readable to the human eye.
Well, that’s it. You’ve learned how Jersey makes it straightforward to compress the REST api responses.
Tip: If you want really learn how to design and implement REST API in Java read the following Tutorial – REST API design and implementation in Java with Jersey and Spring
Keep on coding!!!