Building HATEOAS Links with RESTEasy and JAX-RS

Representational State Transfer (REST) is a design idiom that employs the web’s stateless client-server architecture to represent REST webservices as resources identified by URL. REST-style architectures consist of clients and servers. Clients initiate requests to servers; servers process requests and return appropriate responses. Requests and responses are built around the transfer of “representations” of “resources”. A resource can be any coherent and meaningful concept that may be addressed. A representation of a resource is typically a document that captures the current or intended state of a resource.

(Source: http://en.wikipedia.org/wiki/Representational_State_Transfer.)

Please note that this example application should be deployed on JBOSS 7 server. If you are using any other server, then you will need to update pom.xml and web.xml files as mentioned in this post.
 Table of Contents

What is HATEOAS?
Java REST HATEOAS Example
	Creating maven blank project
	Adding required dependencies in pom.xml
	Registering a new module or service
	Defining GET,PUT,POST and DELETE methods
	Annotating model classes
	Analyze the result

1. What is HATEOAS?

HATEOAS is a constraint on REST that says that a client of a REST application need only know a single fixed URL to access it. Any and all resources should be discoverable dynamically from that URL through hyperlinks included in the representations of returned resources.

Ideally, you should provide the end user only your service root URI. From there onward, user must be able to discover all other URIs in your service. These URIs can be discovered using “link”s in current resource representation. We will see a demonstration of HATEOAS in below given sample application.

Please keep in mind that HATEOAS implementation in given sample project is for demo only. In enterprise level application, any third party API or some custom implementation (preferably using annotations) is recommended.

2. Java REST HATEOAS Example

Let’s create a Java REST application and add HATEOAS links in it’s responses.

2.1. Create maven project

Creating a maven project is as simple as executing below command in command prompt. I am assuming that you have already installed maven in your system.

mvn archetype:generate -DgroupId=com.demo.rest -DartifactId=sampleRestApp -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

If you have not installed maven, please go to maven’s home page and download latest version.

Now convert above project to eclipse supported project. Below command will generate .project file and other eclipse dependencies.

mvn eclipse:eclipse -Dwtpversion=2.0

2.2. Update maven dependencies in pom.xml

Now its time to supply the required dependencies to newly created maven project. Below are the required dependencies. Add them in pom.xml file.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelversion>4.0.0</modelversion>
  <groupid>com.demo.rest</groupid>
  <artifactid>demoResteasyApplication</artifactid>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>demoResteasyApplication Maven Webapp</name>
  <url>http://maven.apache.org</url>

  	<repositories>
	   	<repository>
	      <id>jboss</id>
	      <url>http://repository.jboss.org/maven2</url>
	   	</repository>
	</repositories>

  <dependencies>

    <dependency>
      <groupid>junit</groupid>
      <artifactid>junit</artifactid>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>

    <!-- core library -->
	<dependency>
		<groupid>org.jboss.resteasy</groupid>
		 <artifactid>resteasy-jaxrs</artifactid>
		<version>2.3.1.GA</version>
		<scope>provided</scope>
	</dependency>

   <!-- JAXB support -->
   <dependency>
      <groupid>org.jboss.resteasy</groupid>
      	<artifactid>resteasy-jaxb-provider</artifactid>
      <version>2.3.1.GA</version>
   </dependency>

   <!-- multipart/form-data and multipart/mixed support -->
   <dependency>
      <groupid>org.jboss.resteasy</groupid>
      	<artifactid>resteasy-multipart-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>

  <build>
    <finalname>demoResteasyApplication</finalname>
  </build>

</project>

2.3. Register a new module or service

With release of jax-rs 2.x, we don’t need to specify anything in web.xml. Jax-rs now scan @ApplicationPath annotation for registering new application modules.

package com.demo.rest;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

import com.demo.rest.service.UserService;

@ApplicationPath("/")
public class ApplicationConfig extends Application 
{
    @SuppressWarnings("unchecked")
	public Set<class <?>> getClasses() 
	{
        return new HashSet<class <?>>(Arrays.asList(UserService.class));
    }
}

Our module class looks like this:

@Path("/user-management")
public class UserService 
{
  //Some code
}

Above module registration code will register a new application "/user-management" and will forward all relative relative resource requests to this application / module.

2.4. Define REST methods – GET, PUT, POST and DELETE

As discussed above, REST services maps resource representations and actions which will change their internal representation. These actions shall be seen as equivalent to database SELECT, INSERT, UPDATE and DELETE operations.

If we talk about HTTP protocol, they can be mapped to GET, PUT, POST and DELETE methods. Where:

  • GET method will return a resource representation
  • PUT will modify the internal state of a resource
  • POST is usually for adding a new resource but not essentially
  • DELETE is for removing a resource

Now, lets them understand in terms of user-management module.

  1. A GET should return all users or a single user representation.
  2. A PUT method should be used to modify the representation of a single user.
  3. A POST method should be used to create a new user resource.
  4. And similarly DELETE method should be used to delete a user from system.
package com.demo.rest.service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

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

@Path("/user-management")
public class UserService {

	@GET
	@Path("/")
	@Produces("application/vnd.com.demo.user-management+xml;charset=UTF-8;version=1")
	public UserService getServiceInfo() {
		return new UserService();
	}

	@GET
	@Path("/users")
	@Produces("application/vnd.com.demo.user-management.users+xml;charset=UTF-8;version=1")
	public Users getAllUsers() {
		User user1 = new User();
		user1.setId(1);
		user1.setFirstName("demo");
		user1.setLastName("user");
		user1.setUri("/user-management/users/1");

		User user2 = new User();
		user2.setId(2);
		user2.setFirstName("demo");
		user2.setLastName("user");
		user2.setUri("/user-management/users/2");

		Users users = new Users();
		users.setUsers(new ArrayList());
		users.getUsers().add(user1);
		users.getUsers().add(user2);

		return users;
	}

	@GET
	@Path("/users/{id}")
	@Produces("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	public User getUserById(@PathParam("id") int id) {
		User user = new User();
		user.setId(id);
		user.setFirstName("demo");
		user.setLastName("user");
		user.setUri("/user-management/users/" + id);
		return user;
	}

	@POST
	@Path("/users")
	@Consumes("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	public Response createUser(User user,
			@DefaultValue("false") @QueryParam("allow-admin") boolean allowAdmin)
			throws URISyntaxException {
		System.out.println(user.getFirstName());
		System.out.println(user.getLastName());
		return Response.status(201)
				.contentLocation(new URI("/user-management/users/123")).build();
	}

	@PUT
	// @Path("/users/{id: [0-9]*}")
	@Path("/users/{id}")
	@Consumes("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	@Produces("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	public User updateUser(@PathParam("id") int id, User user)
			throws URISyntaxException {
		user.setId(id);
		user.setFirstName(user.getFirstName() + "updated");
		return user;
	}

	@DELETE
	@Path("/users/{id}")
	public Response deleteUser(@PathParam("id") int id)
			throws URISyntaxException {
		return Response.status(200).build();
	}
}

2.5. Annotate model classes

So far we have created our service class. Now its time to create the resource representation which will be visible to user to play with.

If you remember, HATEOAS insists that your application should have an starting point and thereafter, each interaction of user with application should be an state transfer. The required information for state transfer should come from present resource representation i.e. each re-presentation should provide a mechanism for nest state transfer.

Lets annotation our service and model classes with JAXB annotations, and then we will see, to which degree we have followed HATEOAS guidelines.

//Users.java [Representation of a collection of users]

package com.demo.rest.model;

import java.util.ArrayList;

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

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "users")
public class Users {

	@XmlElement(name="user")
	private ArrayList users;

	public ArrayList getUsers() {
		return users;
	}

	public void setUsers(ArrayList users) {
		this.users = users;
	}
}

//User.java [Representation of a single user]

package com.demo.rest.model;

import java.io.Serializable;

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 {

	private static final long serialVersionUID = 1L;

	@XmlAttribute(name = "id")
	private int id;

	@XmlAttribute(name="uri")
	private String uri;

	@XmlElement(name = "firstName")
	private String firstName;

	@XmlElement(name = "lastName")
	private String lastName;

	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getUri() {
		return uri;
	}
	public void setUri(String uri) {
		this.uri = uri;
	}
}

//UserService.java with JAXB annotation added [Representation of service root]

package com.demo.rest.service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

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

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "user-management")
@Path("/user-management")
public class UserService {
	@XmlElement(name = "users")
	private String uri1 = "/user-management/users";

	@XmlElement(name = "report")
	private String uri2 = "/user-managemet/generate-report";

	public String getUri1() {
		return uri1;
	}

	public void setUri1(String uri1) {
		this.uri1 = uri1;
	}

	public String getUri2() {
		return uri2;
	}

	public void setUri2(String uri2) {
		this.uri2 = uri2;
	}

	@GET
	@Path("/")
	@Produces("application/vnd.com.demo.user-management+xml;charset=UTF-8;version=1")
	public UserService getServiceInfo() {
		return new UserService();
	}

	@GET
	@Path("/users")
	@Produces("application/vnd.com.demo.user-management.users+xml;charset=UTF-8;version=1")
	public Users getAllUsers() {
		User user1 = new User();
		user1.setId(1);
		user1.setFirstName("demo");
		user1.setLastName("user");
		user1.setUri("/user-management/users/1");

		User user2 = new User();
		user2.setId(2);
		user2.setFirstName("demo");
		user2.setLastName("user");
		user2.setUri("/user-management/users/2");

		Users users = new Users();
		users.setUsers(new ArrayList());
		users.getUsers().add(user1);
		users.getUsers().add(user2);

		return users;
	}

	@GET
	@Path("/users/{id}")
	@Produces("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	public User getUserById(@PathParam("id") int id) {
		User user = new User();
		user.setId(id);
		user.setFirstName("demo");
		user.setLastName("user");
		user.setUri("/user-management/users/" + id);
		return user;
	}

	@POST
	@Path("/users")
	@Consumes("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	public Response createUser(User user,
			@DefaultValue("false") @QueryParam("allow-admin") boolean allowAdmin)
			throws URISyntaxException {
		System.out.println(user.getFirstName());
		System.out.println(user.getLastName());
		return Response.status(201)
				.contentLocation(new URI("/user-management/users/123")).build();
	}

	@PUT
	// @Path("/users/{id: [0-9]*}")
	@Path("/users/{id}")
	@Consumes("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	@Produces("application/vnd.com.demo.user-management.user+xml;charset=UTF-8;version=1")
	public User updateUser(@PathParam("id") int id, User user)
			throws URISyntaxException {
		user.setId(id);
		user.setFirstName(user.getFirstName() + "updated");
		return user;
	}

	@DELETE
	@Path("/users/{id}")
	public Response deleteUser(@PathParam("id") int id)
			throws URISyntaxException {
		return Response.status(200).build();
	}
}

2.6. Verify HATEOAS links

So we have written a lot of code for a simple demo. Now, its time to test our code.

I am using RESTClient for verifying the APIs output. You can choose your own way for verification.

I have deployed above application in JBOSS 7.1 server runtime environment running on eclipse juno. If you want to deploy on some standalone jboss instance, you can try that also.

Lets hit the application URLs one by one:

  1. Root service API

    This API returns a representation of service root. It have a uri for user collection and one additional link for API to generate user reports.

    user-management-4567395

  2. Get all users collection

    This representation has a snapshot of user data and uri where all other information of a particular user can be fetched.

    users-1784219

  3. Get user by id

    This representation should provide each relevant detail of user resource and other links if they are.

    user-7693476

  4. Add user without media type

    A user resource, when added, should be added in collection of users. So, implicitly a POST should be available in practically all collection type representations.

    Here, user will be added in users collection so we will post it on “/user-management/users”.

    When adding a post, we must declare the media type we are posting. If we do not specify, following error will occur.

    Response code 415 [Unsupported Media Type]

    create-unsupported-media-type-4296055

  5. Add correct media type in request header

    Lets add correct media type to request header.

    add-content-type-3789827

  6. Create with correct media type

    Now, a new user should be created and a “link’ to created resource should be returned.

    create-success-3066115

  7. Delete user

    When deleting a resource, “HTTP DELETE” is used. It shall not take any media type or request body.

    Resource should be deleted on resource link itself.

    delete-9012031

So, this is how the application state should change between successive API calls through REST HATEOAS and its next state should be directed from current state representation.

I am finishing the writing here and I will leave the task of making this example more fine on you. From below given download link, download above source code files and play with it.

Happy Learning !!

Was this post helpful?

Join 7000+ Awesome Developers

Get the latest updates from industry, awesome resources, blog updates and much more.

* We do not spam !!

18 thoughts on “Building HATEOAS Links with RESTEasy and JAX-RS”

  1. Hi Lokesh,

    Could you post an article for creating a secure REST services, i.e., implementing authentication in REST services.

    Thanks

    Reply
  2. I have the same issue. The app deploys but the index.html hello world works. However, I cannot get to the restful urls? Is there something missing from the app.
    Thanks
    Sam

    Reply
  3. Hi Guptha,
    Currently i am working with RESTFUL web services. can i send serialzable object from client side using
    get method.
    is it possible to send custom object as a @Pathparam using get method and i need json as output using RESTFUL.

    I mean instead of sending all the parameters URI like —-> custom/1/aaa/addr shall we custom/pojo

    Client Side:

    class Pojo{
    int id=1;
    String name=”aaa”;
    String addr=”addr”;
    //setter & getters

    }

    Main class:

    public static void main(String… a)
    {
    Pojo pojo = new Pojo();

    System.out.println(service.path(“rest”).path(“custom/pojo”).accept(MediaType.APPLICATION_JSON).get(String.class));
    }

    server side:

    @Path(“/custom”)
    class Custom
    {
    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Path(“{pojo}”)
    public Pojo getJsonOutput(@PathParam(“pojo”) Pojo p) {
    if(1==p.getId() && “aaa”.equals(p.getName()) && “addr”.equals(p.getAddr())

    return new Pojo();

    else

    return null;
    }
    }

    class Pojo{
    int id=1;
    String name=”aaa”;
    String addr=”addr”;
    String firstName=”aaa”;
    String lastName=”bbb”;
    String firstAddr=”ccc”;
    String lastAddr=”ddd”;
    String mobnumber=”123457896″;

    //Setters & Getters

    }

    Reply
    • Hey gopal, sorry for late reply. i was in some remote location with other priorities. I believe that path params are for constructing the location of resource. they are just the way of feeding some parameters to a method.

      that’s why designers of restful webservices implementation didn’t provide any other mean to replace them in my knowledge. @PathParam is for replacing only one value in resource path and that’s how it should be implemented.

      Reply
  4. First, thanks for the detailed write up, really good work. My question is, where are the hypermedia controls in the generated representations? HATEOAS, or just using hypermedia with REST, recommends using hypermedia controls within the actual resource representations. So if I create an order, perhaps there is a link to approve the order, cancel the order, or modify the order. The hypermedia controls can also change. Is there such a facility in JAX-RS?

    Reply
    • Hi Paul, I am sorry for making so much late reply. I was on a long vacation on year end.

      No, JAX-RS does not provide any such facility till date. You must develop your APIs in a way they seems connected. Let me elaborate how I see the hypermedia controls in resource representations.

      As you see, I have used HTTP protocol for demo. HTTP protocol provides four methods (generally used) i.e. GET, POST, PUT and DELETE. While designing your APIs, you need to make use of special meaning they inherit.

      1) GET is used to fetch a resource.
      2) PUT is used to modify the resource.
      3) POST is used to add a new resource in a “collection of resource.”
      4) DELETE id used to remove the resource.

      As you can see in demo URLs in article, I added a user to collection “user-management/users”. Method used is POST. It is clear that is a user resource has to be created, a http POST call needs to be make in “user-management/users”, and this is actually a URI which returns the users collection when making http GET request.

      Similarly, A particular user is fetched using HTTP GET “user-management/users/{id}”, So if i have to modify this user, I must make a call HTTP PUT “user-management/users/{id}”. And to delete this user HTTP DELETE “user-management/users/{id}”

      So, All CRUD operations of users collection are possible using only two URIs i.e. “user-management/users” and “user-management/users/{id}”.

      If further operations have to be allowed then they must be represented using separate links as i have done using “/user-managemet/generate-report”.

      I hope that I make sense.

      Reply

Leave a Comment

HowToDoInJava

A blog about Java and related technologies, the best practices, algorithms, and interview questions.