Alesandro Lang

This article was contributed by Alesandro Lang

Alesandro Lang is a computer science student in Nuremberg Germany. He is working in a bank datacenter and has a passion for new Java and web technologies.

HTTP Caching in Java with JAX-RS

Last Updated: 28 October 2013

caching http headers jax-rs

Table of Contents

HTTP caching is an easy and oft-ignored option to speed up web applications. It’s standardized and well implemented in all modern browsers and results in a lower latency app and improved responsiveness.

Please see the increasing application performance with HTTP cache headers article for a more thorough discussion of HTTP cache headers.

Java offers a wide variety of frameworks to build a REST-API. This article focuses on implementing HTTP caching with the JAX-RS framework.

If you have questions about Java on Heroku, consider discussing them in the Java on Heroku forums.

Time-based cache headers

In HTTP 1.1 the Cache-Control header specifies the resource caching behavior as well as the max age the resource can be cached. As an example, this response would be cached for one day:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=86400
Last-Modified: Thu, 07 Feb 2013 11:56 EST

Here is a list of all the available Cache-Control tokens and their meaning:

  • private only clients (mostly the browser) and no one else in the chain (like a proxy) should cache this
  • public any entity in the chain can cache this
  • no-cache should not be cached anyway
  • no-store can be cached but should not be stored on disk (most browsers will hold the resources in memory until they will be quit)
  • no-transform the resource should not be modified (for example shrink image by proxy)
  • max-age how long the resource is valid (measured in seconds)
  • s-maxage same like max-age but this value is just for non clients

Setting the Cache-Control header

In a JAX-RS method a Response object can be returned. The content will be auto transformed according to the specified MIME-Type (this is called JAX-RS Content Negotiation). The Cache-Control header can also be set on the Response object by using the CacheControl class. Methods are provided for all of the available Cache-Control header tokens.

@Path("/book/{id}")
@GET
public Response getBook(@PathParam("id") long id){

        Book myBook = getBookFromDB(id);

        CacheControl cc = new CacheControl();
        cc.setMaxAge(86400);
        cc.setPrivate(true);

        ResponseBuilder builder = Response.ok(myBook);
        builder.cacheControl(cc);
        return builder.build();
}

Alt text

Conditional cache headers

Conditional requests are those where the browser can ask the server if it has an updated copy of the resource. The browser will send one or both of the ETag and If-Modified-Since headers about the cached resource it holds. The server can then determine whether updated content should be returned or the browser’s copy is the most recent.

Making a conditional GET

In JAX-RS the Request object has a method to check wether the data based on the date or the etag from the client was modified. The ETag value should be a unique hash of the resource and requires knowledge of internal business logic to construct. One possibility is to use the HashCode of a object as the etag.

@Path("/book/{id}")
@GET
public Response getBook(@PathParam("id") long id, @Context Request request){

        Book myBook = getBookFromDB(id);
        CacheControl cc = new CacheControl();
        cc.setMaxAge(86400);

        EntityTag etag = new EntityTag(Integer.toString(myBook.hashCode()));
        ResponseBuilder builder = request.evaluatePreconditions(etag);

        // cached resource did change -> serve updated content
        if(builder == null){
                builder = Response.ok(myBook);
                builder.tag(etag);
        }

        builder.cacheControl(cc);
        return builder.build();
}

A ResponseBuilder is automatically constructed by calling evaluatePreconditions on the request. If the builder is null, the resource is out of date and needs to be sent back in the response. Otherwise, the preconditions indicate that the client has the latest version of the resource and the 304 Not Modified status code will be automatically assigned.

Alt text

Alt text

Making a conditional PUT

Resource updates can also be conditional. The client would receive 412 Precondition Failed if the ETag send by the client does not match the one on the server. Otherwise the update could be made and a 200 OK with the updated entity or a 204 No Content would be send back.

@Path("/book/{id}")
@PUT
@Consumes("application/json")
public Response getBook(@PathParam("id") long id, @Context Request request, Book updatedBook){

        Book myBook = getBookFromDB(id);
        EntityTag etag = new EntityTag(Integer.toString(myBook.hashCode()));

        ResponseBuilder builder = request.evaluatePreconditions(etag);

        // client is not up to date (send back 412)
        if(builder != null){
                return builder.build();
        }

        updateBookInDB(updatedBook);

        builder = Response.noContent();
        return builder.build();
}

Alt text