How to write RESTful web services using jax-rs and jaxb in java [HATEOAS example]

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.)

Sections in this post:
HATEOAS
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

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 onwards, 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.

I will write about my understanding on HATEOAS on next post, to keep this post in size limit.

Source code Download

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.

Creating maven blank 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

Adding required dependencies in pom.xml

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

I am giving whole pom.xml for reference.

<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>

Now again run mvn command to add dependencies in project, so that eclipse can resolve them.

mvn eclipse:eclipse -Dwtpversion=2.0

Registering a new module or service

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

Lets register our one module in such manner.

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.

Defining GET, PUT, POST and DELETE methods

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. A GET should return all users or a single user representation. A PUT method should be used to modify the representation of a single user. A POST method should be used to create a new user resource. And. similarly DELETE method should be used to delete a user from system.

Lets see them in code:

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();
	}
}

Annotating 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();
	}
}

Analyze the results

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 root api

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 collection

3) Get user by id

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

get user by id

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

5) Add correct media type in request header

Lets add correct media type to request header.

add-content-type

6) Create with correct media type

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

create-success with correct media type

8) 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 user

So, this is how the application state should change between successive API calls 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 them.

Source code Download

Happy Learning !!

10 thoughts on “How to write RESTful web services using jax-rs and jaxb in java [HATEOAS example]”

    1. Oops !! You are right. Users should also be serializable. In fact all classes which represents the model and are processed by JAXB should be made Serializable.

  1. 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

    }

    1. 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.

  2. 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?

    1. 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.

          1. Hi Lokesh,

            Please find the steps below i have followed to run the application.

            I have downloaded your source code and created the war file using maven.
            I have deployed the war file in tomcat and accessed the application.
            Able to access the index page i.e. Hello world using the url – http://localhost:8080/demoResteasyApplication.

            How ever i could not analyze the results (restful services)using rest client but only facing 404 error for below urls -
            http://localhost:8080/demoResteasyApplication//user-management
            http://localhost:8080/demoResteasyApplication//user-management/users

            1. Can you please check your server logs in console? They must have something useful. If nothing works, please send the war file to my mail id. I will check it.
              In between, I am assuming that double slashes (//) you mentioned in above URLs are typing mistakes.

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

Want to ask any question? Or suggest anything?