How to cache response with Jersey?

Summary of solutions:

  1. Request as method parameter

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public Response getMyEntity(@Context final Request request);
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Override
         public Response getMyEntity(final Request request) {
    
             final MyEntity myEntity = ... // load entity
             final String eTagValue = ... // calclutate value of ETag
    
             final EntityTag eTag = new EntityTag(eTagValue);
    
             ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
    
             if (responseBuilder == null) {
                 return Response.ok(user).tag(eTag).build();
             }
    
             return responseBuilder.build();
         }
     }
    

    Disadvantages:

    • implementation detail Requestis exposed

    • return type Reponse is generic

    • missing grammar of return type in WADL

    • client proxy with unnecessary parameter Request

  2. Request as instance variable

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public Response getMyEntity();
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Context
         private Request request
    
         @Override
         public Response getMyEntity() {
    
             final MyEntity myEntity = ... // load entity
             final String eTagValue = ... // calclutate value of ETag
    
             final EntityTag eTag = new EntityTag(eTagValue);
    
             ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
    
             if (responseBuilder == null) {
                 return Response.ok(user).tag(eTag).build();
             }
    
             return responseBuilder.build();
         }
     }
    

    Disadvantages:

    • return type Reponse is generic

    • missing grammar of return type in WADL

    • dependency injection with @Context is complicated, see https://stackoverflow.com/questions/33240443

  3. ShallowEtagHeaderFilter as web filter

    web.xml:

     <filter>
         <filter-name>etagFilter</filter-name>
         <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
     </filter>
     <filter-mapping>
         <filter-name>etagFilter</filter-name>
         <url-pattern>/api/*</url-pattern>
     </filter-mapping>
    

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public MyEntity getMyEntity();
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Override
         public MyEntity getMyEntity() {
    
             final MyEntity myEntity = ... // load entity
             return myEntity;
         }
     }
    

    Disadvantages:

    • bad server performance, see JavaDoc

    • works only on uncommitted response

    • no support of weak ETag

  4. Custom WriterInterceptor as JAX-RS Interceptor

    Interceptor:

     public class CustomInterceptor implements WriterInterceptor {
    
         @Context
         private Request request;
    
         @Override
         public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
    
             OutputStream old = context.getOutputStream();
    
             ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
             try {
    
                 context.setOutputStream(buffer);
                 context.proceed();
    
                 byte[] entity = buffer.toByteArray();
    
                 String etag = ... // calclutate value of ETag
                 context.getHeaders().putSingle(HttpHeaders.ETAG, etag);
    
                 ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
    
                 if (responseBuilder == null) {
                      throw new WebApplicationException(responseBuilder.status(Response.Status.NOT_MODIFIED).header(HttpHeaders.ETAG, etag).build());
                 }
    
                 old.write(entity);
    
             } finally {
                 context.setOutputStream(old);
             }
         }
     }
    

    See also: ServerCacheInterceptor (Resteasy)

    Interface:

     @Path("myentity")
     public interface MyEntityResource
    
         @GET
         @Produces(MediaType.APPLICATION_JSON)
         public MyEntity getMyEntity();
     }
    

    Implementation:

     public class MyEntityResourceImpl implements MyEntityResource
    
         @Override
         public MyEntity getMyEntity() {
    
             final MyEntity myEntity = ... // load entity
             return myEntity;
         }
     }
    

    Disadvantages:

    • no predefined interceptor for Jersey available

    • bad server performance

    • no support of weak ETag

    • ugly workaround with WebApplicationException


You can use CacheControl, eTag - follow below example code

// In your jersey method
    final EntityTag eTag = new EntityTag(resource.getId() + "_" +
     resource.getLastModified().getTime());
    final CacheControl cacheControl = new CacheControl();
    cacheControl.setMaxAge(-1);

    ResponseBuilder builder = request.evaluatePreconditions(
         resource.getLastModified(), eTag);

    // the resoruce's information was modified, return it
    if (builder == null) {
         builder = Response.ok(resource);
    }

    // the resource's information was not modified, return a 304

    return builder.cacheControl(cacheControl).lastModified(
         resource.getLastModified()).tag(eTag).build();

Replace resource with your Resource instance.


Recently I've been solving a similar (if not the same) problem. As a side result of it the following library emerged: https://github.com/AndreyLebedenko/dropwizard-caching-filter

The way it can be used is like below:

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/cached")
@ResponseCachedByFilter(10000)
public Object getCached() {
    return dao.get();
} 

Hope it helps.


You could use any caching mechanism applicable for standard java together with Jersey, like Ehcache.

You only have to pay attention to verify that your data in the backend hasn't changed.

Here is simple example with Ehcache:

@GET
@Path("{id}")
public List<Data> getData(@PathParam("id") Long id) {
    Element element = CacheManager.getInstance().getCache("test").get(id);
    if(element == null) {
        Data value = fetchElementFromBackend(id);
        CacheManager.getInstance().getCache("test").put(new Element(id, value));
        return value;
    }

    return element.getObjectValue();
}

Tags:

Java

Rest

Jersey