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>https://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 !!

Comments

Subscribe
Notify of
guest
9 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode