JAX-RS RESTEasy Cache control with ETag example

ETags or entity tags are useful HTTP headers which can help in building a super fast application by minimizing the server load on system. ETag is set to the response to the client so a client can use various control request headers such as If-Match and If-None-Match for conditional requests. javax.ws.rs.core.Response.ResponseBuilder#tag() and javax.ws.rs.core.EntityTag are useful classes to work on ETags.

On server side, a unchanged ETag (match done between ETag attached with HTTP request and ETag calculated for requested resource) simply means, resource is unchanged from last requested time, so simply sending a HTTP 304 header [Not Modified] will be enough so that client can use the local available copy of resource without worrying much.

To demonstrate the example, I have two REST APIs in service class.

a) GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

This API will get the user resource which will have a ETag attached to it. This ETag will be used by server to verify if user details have been updated sincl last request or not.

b) PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

This API will make some update on User resource on server, which will force the server to return the new copy of resource again.

Lets build the example step by step.

Step 1) Create a new java web project using maven and add following dependencies.

<repositories>
	<repository>
	  <id>jboss</id>
	  <url>http://repository.jboss.org/maven2</url>
	</repository>
</repositories>
<dependencies>
	<!-- core library -->
	<dependency>
		<groupId>org.jboss.resteasy</groupId>
		<artifactId>resteasy-jaxrs</artifactId>
		<version>2.3.1.GA</version>
	</dependency>
	<!-- JAXB support -->
	<dependency>
		<groupId>org.jboss.resteasy</groupId>
		<artifactId>resteasy-jaxb-provider</artifactId>
		<version>2.3.1.GA</version>
	</dependency>
	<dependency>
		<groupId>net.sf.scannotation</groupId>
		<artifactId>scannotation</artifactId>
		<version>1.0.2</version>
	</dependency>
</dependencies>

Step 2) Make a class to represent User Resource

Make sure to provide some logic to verify the last updated time when this resource was modified. I have added one field lastModied of Date type.

package com.howtodoinjava.demo.rest.model;

import java.io.Serializable;
import java.util.Date;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "user")
public class User implements Serializable 
{
	@XmlAttribute(name = "id")
	private int id;
	
	@XmlAttribute(name="uri")
	private String uri;
		
	@XmlElement(name = "firstName")
	private String firstName;
	
	@XmlElement(name = "lastName")
	private String lastName;
	
	@XmlElement(name="last-modified")
	private Date lastModified;
	
	//Setters and Getters

Step 3) DAO layer with methods to access and modify the resource

I have used a static HashMap for example purpose. In real time applications, It will be a data base. I have added only one user in Map for example.

package com.howtodoinjava.demo.rest.data;

import java.util.Date;
import java.util.HashMap;

import com.howtodoinjava.demo.rest.model.User;

public class UserDatabase 
{
	public static HashMap<Integer, User> users = new HashMap<Integer, User>();
	static 
	{
		User user = new User();
		user.setId(1);
		user.setFirstName("demo");
		user.setLastName("user");
		user.setUri("/user-management/users/1");
		user.setLastModified(new Date());
		users.put(1, user);
	}
	
	public static User getUserById(Integer id)
	{
		return users.get(id);
	}
	
	public static void updateUser(Integer id)
	{
		User user = users.get(id);
		user.setLastModified(new Date());
	}
	
	public static Date getLastModifiedById(Integer id)
	{
		return users.get(id).getLastModified();
	}
}

Step 4) Write Restful APIs to access and modify the resource

This is the main step. I have written above described two APIs. GET API return the user resource with ETag attached to it. This ETag is calculated on last modified date of User resource. This will ensure that everytime User is updated, a new ETag will be generated.

package com.howtodoinjava.demo.rest.service;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;

import com.howtodoinjava.demo.rest.data.UserDatabase;

@Path("/user-service")
public class UserService 
{
	@GET
	@Path("/users/{id}")
	public Response getUserById(@PathParam("id") int id, @Context Request req) 
	{
		//Create cache control header
		 CacheControl cc = new CacheControl();
		 //Set max age to one day
	     cc.setMaxAge(86400);
	        
		Response.ResponseBuilder rb = null;
		
		//Calculate the ETag on last modified date of user resource  
		EntityTag etag = new EntityTag(UserDatabase.getLastModifiedById(id).hashCode()+"");
		
		//Verify if it matched with etag available in http request
        rb = req.evaluatePreconditions(etag);
        
        //If ETag matches the rb will be non-null; 
        //Use the rb to return the response without any further processing
        if (rb != null) 
        {
            return rb.cacheControl(cc).tag(etag).build();
        }
        
        //If rb is null then either it is first time request; or resource is modified
        //Get the updated representation and return with Etag attached to it
        rb = Response.ok(UserDatabase.getUserById(id)).cacheControl(cc).tag(etag);
		return rb.build();
	}
	
	@PUT
	@Path("/users/{id}")
	public Response updateUserById(@PathParam("id") int id) 
	{
		//Update the User resource
		UserDatabase.updateUser(id);
		return Response.status(200).build();
	}
}
Caution: The granularity of dates used in HTTP headers is not as precise as some dates used in data sources.  
For example, the precision for a date in a database row might be defined to the millisecond. However, the date 
in an HTTP header field is only precise to seconds. When evaluating HTTP preconditions, if you compare a 
java.util.Date object directly to the date in an HTTP header, the difference in precision might produce 
unexpected results. 

To avoid this problem, use the hashcode of date object or use some normalize form.

Test the example code

1) First time request : GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

User resource request first time

User resource request first time

2) Subsequent request(s) : GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

User resource subsequent  requests

User resource subsequent requests

3) Modify request : PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

User resource Updated using PUT API

User resource Updated using PUT API

4) Updated Resource : GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

Update User resource retrieved

Update User resource retrieved

To download the sourcecode of above example, click on given below link.

Sourcecode Download

Happy Learning !!

6 thoughts on “JAX-RS RESTEasy Cache control with ETag example”

  1. How can we make the second request return a 304 not modified response instead? Do we need to update anything server side or is it just the client who needs to send the correct cache control headers?

Note:- In comment box, please put your code inside [java] ... [/java] OR [xml] ... [/xml] tags otherwise it may not appear as intended.

Leave a Reply

Your email address will not be published. Required fields are marked *


seven − = 4

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>