About Adrian Matei

Adrian Matei (ama [AT] codingpedia DOT org) is the founder of Podcastpedia.org and Codingpedia.org, computer science engineer, husband, father, curious and passionate about science, computers, software, education, economics, social equity, philosophy.

Tutorial – REST API design and implementation in Java with Jersey and Spring

Looking to REST in Java? Then you’ve come to the right place, because in the blog post I will present you how to “beautifully” design a REST API and also, how to implement it in Java with the Jersey framework. The RESTful API developed in this tutorial will demonstrate a complete Create,_read,_update_and_delete (CRUD) functionality for podcast resources stored in a MySql database.
 
 
 
 
 
 

1. The example

1.1. Why?

Before we start, let me tell you why I’ve written this post – well, my intention is to offer in the future a REST API for Podcastpedia.org. Of course I could use Spring’s own REST implementation, as I currently do for the AJAX calls, but I wanted also to see how the “official” implementation looks like. So, the best way to get to know the technology is to build a prototype with it. That is what I did and what I am presenting here, and I can say that I am pretty damn satisfied with Jersey. Read along to understand why!!!

Note: You can visit my post Autocomplete search box with jQuery and Spring MVC to see how Spring handles REST requests.

1.2. What does it do?

The resource managed in this tutorial are podcasts. The REST API will allow creation, retrieval, update and deletion of such resources.

1.3. Architecture and technologies

Rest-Demo-Diagram

The demo application uses a multi-layered architecture, based on the “Law of Demeter (LoD) or principle of least knowledge”[16]:

  • the first layer is the REST support implemented with Jersey, has the role of a facade and delegates the logic to the business layer
  • the business layer is where the logic happens
  • the data access layer is where the communcation with the pesistence storage (in our case the MySql database) takes place

A few words on the technologies/frameworks used:

1.3.1. Jersey (Facade)

The Jersey RESTful Web Services framework is open source, production quality, framework for developing RESTful Web Services in Java that provides support for JAX-RS APIs and serves as a JAX-RS (JSR 311 & JSR 339) Reference Implementation.

1.3.2. Spring (Business layer)

I like glueing stuff together with Spring, and this example makes no exception. In my opinion there’s no better way to make POJOs with different functionalities. You’ll find out in the tutorial what it takes to integrate Jersey 2 with Spring.

1.3.3. JPA 2 / Hibernate (Persistence layer)

For the persistence layer I still use a DAO pattern, even though for implementing it I am using JPA 2, which, as some people say, should make DAOs superfluous (I, for one, don’t like my service classes cluttered with EntityManager/JPA specific code). AS supporting framework for JPA 2 I am using Hibernate.

See my post Java Persistence Example with Spring, JPA2 and Hibernate for an interesting discussion around persistence thema in Java.

1.3.4. Web Container

Everything gets packaged with Maven as a .war file and can be deployed on any web container – I used Tomcat and Jetty but, it could also be Glassfih, Weblogic, JBoss or WebSphere.

1.3.5. MySQL

The sample data is stored in a MySQL table:

database-schema

1.3.6. Technology versions

  1. Jersey 2.9
  2. Spring 4.0.3
  3. Hibernate 4
  4. Maven 3
  5. Tomcat 7
  6. Jetty 9
  7. MySql 5.6

Note: The main focus in the post will be on the REST api design and its implementation with the Jersey JAX-RS implementation, all the other technologies/layers are considered as enablers.

1.4. Source code

The source code for the project presented here is available on GitHub, with complete instructions on how to install and run the project:

2. Configuration

Before I start presenting the design and implementation of the REST API, we need to do a little configuration so that all these wonderful technologies can come and play together

2.1. Project dependencies

The Jersey Spring extension must be present in your project’s classpath. If you are using Maven add it to the pom.xml file of your project:

Jersey-spring dependency in the pom.xml

<dependency>
	<groupId>org.glassfish.jersey.ext</groupId>
	<artifactId>jersey-spring3</artifactId>
	<version>${jersey.version}</version>
	<exclusions>
		<exclusion>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
		</exclusion>			
		<exclusion>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
		</exclusion>
	</exclusions>			
</dependency>
<dependency>
	<groupId>org.glassfish.jersey.media</groupId>
	<artifactId>jersey-media-json-jackson</artifactId>
	<version>2.4.1</version>
</dependency>

Note: The jersey-spring3.jar, uses its own version for Spring libraries, so to use the ones you want (Spring 4.0.3.Release in this case), you need to exclude these libraries manually.

Code alert: If you want to see what other dependencies are needed (e.g. Spring, Hibernate, Jetty maven plugin, testing etc.) in the project you can have a look at the the complete pom.xml file available on GitHub.

2.2. web.xml

Web Application Deployment Descriptor

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<display-name>Demo - Restful Web Application</display-name>

	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/applicationContext.xml</param-value>
	</context-param>

	<servlet>
		<servlet-name>jersey-serlvet</servlet-name>
		<servlet-class>
			org.glassfish.jersey.servlet.ServletContainer
		</servlet-class>
		<init-param>
			<param-name>javax.ws.rs.Application</param-name>
			<param-value>org.codingpedia.demo.rest.RestDemoJaxRsApplication</param-value>			
		</init-param>		
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>jersey-serlvet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>

	<resource-ref>
        <description>Database resource rest demo web application </description>
        <res-ref-name>jdbc/restDemoDB</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>	
</web-app>

2.2.1. Jersey-servlet

Notice the Jersey servlet configuration [lines 18-33]. The javax.ws.rs.core.Application class defines the components (root resource and provider classes,) of the JAX-RS application. I used ResourceConfig, which is Jersey’s own implementation of the class Application, and which provides advanced capabilites to simplify registration of JAX-RS components. Check out the JAX-RS Application Model in the documentation for more possibilities.

My implementation of the ResourceConfig class, org.codingpedia.demo.rest.RestDemoJaxRsApplication, registers application resources, filters, exception mappers and feature :

org.codingpedia.demo.rest.service.MyDemoApplication

package org.codingpedia.demo.rest.service;

//imports omitted for brevity 

/**
 * Registers the components to be used by the JAX-RS application
 * 
 * @author ama
 * 
 */
public class RestDemoJaxRsApplication extends ResourceConfig {

	/**
	 * Register JAX-RS application components.
	 */
	public RestDemoJaxRsApplication() {
		// register application resources
		register(PodcastResource.class);
		register(PodcastLegacyResource.class);

		// register filters
		register(RequestContextFilter.class);
		register(LoggingResponseFilter.class);
		register(CORSResponseFilter.class);

		// register exception mappers
		register(GenericExceptionMapper.class);
		register(AppExceptionMapper.class);
		register(NotFoundExceptionMapper.class);

		// register features
		register(JacksonFeature.class);
		register(MultiPartFeature.class);
	}
}

Please note the:

  • org.glassfish.jersey.server.spring.scope.RequestContextFilter, which is a Spring filter that provides a bridge between JAX-RS and Spring request attributes
  • org.codingpedia.demo.rest.resource.PodcastsResource, which is the “facade” component that exposes the REST API via annotations and will be thouroughly presented later in the post
  • org.glassfish.jersey.jackson.JacksonFeature, which is a feature that registers Jackson JSON providers – you need it for the application to understand JSON data

2.1.2.2. Spring application context configuration

The Spring application context configuration is located in the classpath under spring/applicationContext.xml:

Spring application context configuration

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"	
	xsi:schemaLocation="

http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx.xsd


http://www.springframework.org/schema/context

		http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="org.codingpedia.demo.rest.*" />

	<!-- ************ JPA configuration *********** -->
	<tx:annotation-driven transaction-manager="transactionManager" />  
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <bean id="transactionManagerLegacy" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoryLegacy" />
    </bean>    
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="classpath:config/persistence-demo.xml" />
        <property name="persistenceUnitName" value="demoRestPersistence" />        
        <property name="dataSource" ref="restDemoDS" />
        <property name="packagesToScan" value="org.codingpedia.demo.*" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="true" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
            </bean>
        </property>
    </bean>     
    <bean id="entityManagerFactoryLegacy" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="classpath:config/persistence-demo.xml" />
        <property name="persistenceUnitName" value="demoRestPersistenceLegacy" />
        <property name="dataSource" ref="restDemoLegacyDS" />
        <property name="packagesToScan" value="org.codingpedia.demo.*" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="true" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
            </bean>
        </property>
    </bean>        

	<bean id="podcastDao" class="org.codingpedia.demo.rest.dao.PodcastDaoJPA2Impl"/>	
    <bean id="podcastService" class="org.codingpedia.demo.rest.service.PodcastServiceDbAccessImpl" />	
    <bean id="podcastsResource" class="org.codingpedia.demo.rest.resource.PodcastsResource" />
    <bean id="podcastLegacyResource" class="org.codingpedia.demo.rest.resource.PodcastLegacyResource" />

	<bean id="restDemoDS" class="org.springframework.jndi.JndiObjectFactoryBean" scope="singleton">
	    <property name="jndiName" value="java:comp/env/jdbc/restDemoDB" />
	    <property name="resourceRef" value="true" />        
	</bean>
	<bean id="restDemoLegacyDS" class="org.springframework.jndi.JndiObjectFactoryBean" scope="singleton">
	    <property name="jndiName" value="java:comp/env/jdbc/restDemoLegacyDB" />
	    <property name="resourceRef" value="true" />        
	</bean>	
</beans>

Nothing special here, it just defines the beans that are needed throughout the demo application (e.g. podcastsResource which is the entry point class for our REST API).

3. The REST API (design & implementation)

3.1. Resources

3.1.1. Design

As mentioned earlier, the demo application manages podcasts, which represent the resource in our REST API. Resources are the central concept in REST and are characterized by two main things:

  • each is referenced with a global identifier (e.g. a URI in HTTP).
  • has one or more representations, that they expose to the outer world and can be manipulated with (we’ll be working mostly with JSON representations in this example)

Resources are usually represented in REST by nouns (podcasts, customers, user, accounts etc.) and not verbs (getPodcast, deleteUser etc.)

The endpoints used throughout the tutorial are :

  • /podcasts(notice the plural) URI identifying a resource representing a collection of podcasts
  • /podcasts/{id} – URI identifying a podcast resource, by the podcast’s id

3.1.2. Implementation

For the sake of simplicity, a podcast will have only the following properties:

  • id – uniquely identifies the podcast
  • feed – url feed of the podcast
  • title – title of the podcast
  • linkOnPodcastpedia – where you can find the podcast on Podcastpedia.org
  • description – a short description of the podcast

I could have used only one Java class for the representation of the podcast resource in the code, but in that case the class and its properties/methods would have gotten cluttered with both JPA and XML/JAXB/JSON annotations. I wanted to avoid that and I used two representations which have pretty much the same properties instead:

  • PodcastEntity.java – JPA annotated class used in the DB and business layers
  • Podcast.java – JAXB/JSON annotated class used in the facade and business layers

Note: I am still trying to convince myself that this is the better approach, so if you have a suggestion on this please leave a comment.

The Podcast.java classs look something like the following:

Podcast.java

package org.codingpedia.demo.rest.resource;

//imports omitted for brevity

/**
 * Podcast resource placeholder for json/xml representation 
 * 
 * @author ama
 *
 */
@SuppressWarnings("restriction")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Podcast implements Serializable {

    private static final long serialVersionUID = -8039686696076337053L;

    /** id of the podcast */
    @XmlElement(name = "id")    
    private Long id;
    
    /** title of the podcast */
    @XmlElement(name = "title")    
    private String title;
        
    /** link of the podcast on Podcastpedia.org */
    @XmlElement(name = "linkOnPodcastpedia")    
    private String linkOnPodcastpedia;
    
    /** url of the feed */
    @XmlElement(name = "feed")    
    private String feed;
    
    /** description of the podcast */
    @XmlElement(name = "description")
    private String description; 
        
    /** insertion date in the database */
    @XmlElement(name = "insertionDate")
    @XmlJavaTypeAdapter(DateISO8601Adapter.class)    
    @PodcastDetailedView
    private Date insertionDate;

    public Podcast(PodcastEntity podcastEntity){
        try {
            BeanUtils.copyProperties(this, podcastEntity);
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public Podcast(String title, String linkOnPodcastpedia, String feed,
            String description) {
        
        this.title = title;
        this.linkOnPodcastpedia = linkOnPodcastpedia;
        this.feed = feed;
        this.description = description;
        
    }
    
    public Podcast(){}

//getters and setters now shown for brevity
}

and translates into the following JSON representation, which is actually the de facto media type used with REST nowadays:

{
	"id":1,
	"title":"Quarks & Co - zum Mitnehmen-modified",
	"linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/1/Quarks-Co-zum-Mitnehmen",
	"feed":"http://podcast.wdr.de/quarks.xml",
	"description":"Quarks & Co: Das Wissenschaftsmagazin",
	"insertionDate":"2014-05-30T10:26:12.00+0200"
}

Even though JSON is becoming more and more the preffered representation in REST APIs, you shouldn’t neglect the XML representation, as most of the systems still use XML format for communication with other parties.

The good thing is that in Jersey you can kill two rabbits with one shot – with JAXB beans (as used above) you will be able to use the same Java model to generate JSON as well as XML representations. Another advantage is simplicity of working with such a model and availability of the API in Java SE Platform.

Note: Most of the methods defined in this tutorial will produce and consume also the application/xml media type, with application/json being the preferred way.

3.2. Methods

Before I present you the API, let me to tell you that

  • Create = POST
  • Read = GET
  • Update = PUT
  • Delete = DELETE

and is not a strict 1:1 mapping. Why? Because you can also use PUT for Creation and POST for Update. This will be explained and demonstrated in the coming paragraphs.

Note: For Read and Delete it is pretty clear, they map indeed one to one with the GET and DELETE HTTP operations. Anyway REST is an architectural style, is not a specification and you should adapt the architecture to your needs, but if you want to make your API public and have somebody willing to use it, you should follow some “best practices”.

As already mentioned the PodcastRestResource class is the one handling all the rest requests:

package org.codingpedia.demo.rest.resource;
//imports
......................
@Component
@Path("/podcasts")
public class PodcastResource {
    @Autowired
    private PodcastService podcastService;
    .....................
}

Notice the @Path("/podcasts") before the class definition – everything related to podcast resources will occur under this path. The @Path annotation’s value is a relative URI path. In the example above, the Java class will be hosted at the URI path /podcasts. The PodcastService interface exposes the business logic to the REST facade layer.

Code alert: You can find the entire content of the class on GitHub – PodcastResource.java. We’ll be going through the file step by step and explain the different methods corresponding to the different operations.

3.2.1. Create podcast(s)

3.2.1.1. Design

While the “most known” way for resource creation is by using POST, As mentioned before to create a new resource I could use both the POST and PUT methods, and I did just that:

  Description  URI  HTTP method
  HTTP Status response
 Add new podcast /podcasts/POST201 Created
 Add new podcast (all values must be sent) /podcasts/{id}PUT201 Created

 
The big difference between using POST (not idempotent)

“The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line[…] If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header” [1]

and PUT (idempotent)

“The PUT method requests that the enclosed entity be stored under the supplied Request-URI […] If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response.” [1]

is that for PUT you should know beforehand the location where the resource will be created and send all the possible values of the entry.

3.2.1.2. Implementation

3.2.1.2.1. Create a single resource with POST

Create a single podcast resource from JSON

/**
 * Adds a new resource (podcast) from the given json format (at least title
 * and feed elements are required at the DB level)
 * 
 * @param podcast
 * @return
 * @throws AppException
 */
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response createPodcast(Podcast podcast) throws AppException {
	Long createPodcastId = podcastService.createPodcast(podcast);
	return Response.status(Response.Status.CREATED)// 201
			.entity("A new podcast has been created")
			.header("Location",
					"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
							+ String.valueOf(createPodcastId)).build();
}

Annotations

  • @POST – indicates that the method responds to HTTP POST requests
  • @Consumes({MediaType.APPLICATION_JSON}) – defines the media type, the method accepts, in this case "application/json"
  • @Produces({MediaType.TEXT_HTML}) – defines the media type) that the method can produce, in this case "text/html".

Response

  • on success: text/html document, with a HTTP status of 201 Created, and a Location header specifying where the resource has been created
  • on error:
    • 400 Bad request if not enough data is provided
    • 409 Conflict if on the server side is determined a podcast with the same feed exists

3.2.1.2.2. Create a single resource (“podcast”) with PUT

This will be treated in the Update Podcast section below.

3.2.1.2.3. Bonus – Create a single resource (“podcast”) from form

Create a single podcast resource from form

/**
 * Adds a new podcast (resource) from "form" (at least title and feed
 * elements are required at the DB level)
 * 
 * @param title
 * @param linkOnPodcastpedia
 * @param feed
 * @param description
 * @return
 * @throws AppException
 */
@POST
@Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
@Produces({ MediaType.TEXT_HTML })
@Transactional
public Response createPodcastFromApplicationFormURLencoded(
		@FormParam("title") String title,
		@FormParam("linkOnPodcastpedia") String linkOnPodcastpedia,
		@FormParam("feed") String feed,
		@FormParam("description") String description) throws AppException {

	Podcast podcast = new Podcast(title, linkOnPodcastpedia, feed,
			description);
	Long createPodcastid = podcastService.createPodcast(podcast);

	return Response
			.status(Response.Status.CREATED)// 201
			.entity("A new podcast/resource has been created at /demo-rest-jersey-spring/podcasts/"
					+ createPodcastid)
			.header("Location",
					"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
							+ String.valueOf(createPodcastid)).build();
}

Annotations

    • @POST – indicates that the method responds to HTTP POST requests
    • @Consumes({MediaType.APPLICATION_FORM_URLENCODED})- defines the media type, the method accepts, in this case"application/x-www-form-urlencoded"
      • @FormParam – present before the input parameters of the method, this annotation binds the value(s) of a form parameter contained within a request entity body to a resource method parameter. Values are URL decoded unless this is disabled using the Encoded annotation
  • @Produces({MediaType.TEXT_HTML}) – defines the media type that the method can produce, in this case “text/html”. The response will be a html document, with a status of 201, indicating to the caller that the request has been fulfilled and resulted in a new resource being created.

Response

  • on success: text/html document, with a HTTP status of 201 Created, and a Location header specifying where the resource has been created
  • on error:
    • 400 Bad request if not enough data is provided
    • 409 Conflict if on the server side is determined a podcast with the same feed exists

3.2.2. Read podcast(s)

3.2.2.1. Design

The API supports two Read operations:

  • return a collection of podcasts
  • return a podcast identified by id
 Description URI HTTP method
 HTTP Status response
 Return all podcasts /podcasts/?orderByInsertionDate={ASC|DESC}&numberDaysToLookBack={val}GET200 OK
 Add new podcast (all values must be sent) /podcasts/{id}GET200 OK

Notice the query parameters for the collection resource – orderByInsertionDate and numberDaysToLookBack. It makes perfect sense to add filters as query parameters in the URI and not be part of the path.

3.2.2.2. Implementation

3.2.2.2.1. Read all podcasts (“/”)

Read all resources

/**
 * Returns all resources (podcasts) from the database
 * 
 * @return
 * @throws IOException
 * @throws JsonMappingException
 * @throws JsonGenerationException
 * @throws AppException
 */
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public List<Podcast> getPodcasts(
		@QueryParam("orderByInsertionDate") String orderByInsertionDate,
		@QueryParam("numberDaysToLookBack") Integer numberDaysToLookBack)
		throws JsonGenerationException, JsonMappingException, IOException,
		AppException {
	List<Podcast> podcasts = podcastService.getPodcasts(
			orderByInsertionDate, numberDaysToLookBack);
	return podcasts;
}

Annotations

  • @GET - indicates that the method responds to HTTP GET requests
  • @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) – defines the media type) that the method can produce, in this case either "application/json" or "application/xml"(you need the @XmlRootElement in front of the Podcast class ). The response will be a list of podcasts either in JSON or XML format.

Response

  • list of podcasts from the database and a HTTP Status of 200 OK

3.2.2.2.1. Read one podcast

Read one resource by id

@GET
@Path("{id}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getPodcastById(@PathParam("id") Long id)
		throws JsonGenerationException, JsonMappingException, IOException,
		AppException {
	Podcast podcastById = podcastService.getPodcastById(id);
	return Response.status(200).entity(podcastById)
			.header("Access-Control-Allow-Headers", "X-extra-header")
			.allow("OPTIONS").build();
}

Annotations

  • @GET – indicates that the method responds to HTTP GET requests
  • @Path("{id}") – identifies the URI path that the class method will serve requests for. The “id” value is an embedded variable making an URI path template. It is used in combination with the @PathParam variable.
    • @PathParam("id") – binds the value of a URI template parameter (“id”) to the resource method parameter. The value is URL decoded unless this is di sabled using the @Encoded annotation. A default value can be specified using the @DefaultValue annotation.
  • @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) – defines the media type) that the method can produce, in this case "application/json" or "application/xml"(you need the @XmlRootElement in front of the Podcast class ).

Response

  • on success: requested podcast with a 200 OK HTTP status. The format is either xml or JSON, depending on the Accept -header’s value sent by the client (might bet application/xml or application/json)
  • on error: 404 Not found if the podcast with the given id does not exist in the database

3.2.3. Update podcast

3.2.3.1. Design

DescriptionURIHTTP method
HTTP Status response
 Update podcast (fully) /podcasts/{id}PUT200 OK
 Update podcast (partially) /podcasts/{id}POST200 OK

 
In the REST arena you will be doing two kind of updates:

  1. full updates – that is where you will provide all the
  2. partial updates – when only some properties will be sent over the wire for update

For full updates, it’s pretty clear you can use the PUT method and you are conform the method’s specification in the RFC 2616.

Now for the partial update there’s a bunch of proposals/debate on what to use:

  1. via PUT
  2. via POST
  3. via PATCH

Let me tell why I consider the first option (with PUT) is a NO GO. Well, accordingly to the specification

“If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.“[1]

if I would like to update just the title property of the podcast with the id 2

PUT command for partial update

PUT http://localhost:8888/demo-rest-jersey-spring/podcasts/2 HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 155
Host: localhost:8888
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

{
	"title":"New Title"
}

then, accordingly to the specification the resource “stored” at the location should have only id and title, and clearly my intent was not that.

The second option via POST… well we can “abuse” this one and that is exactly what I did in the implementation, but it does not seem conform to me, because the spec for POST states:

“The posted entity is subordinate to that URI in the same way that a file is subordinate to a directory containing it, a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate to a database.“[1]

That does not look like a partial update case to me…

The third option is to use PATCH,  and I guess this is the main reason the method came to life:

“Several applications extending the Hypertext Transfer Protocol (HTTP)
   require a feature to do partial resource modification.  The existing
   HTTP PUT method only allows a complete replacement of a document.
   This proposal adds a new HTTP method, PATCH, to modify an existing
   HTTP resource.”[2]

I am pretty sure this will be used in the future for partial updates, but since is not yet part of the specification and not yet implemented in Jersey I chose to use the second option with POST for this demo. If you really want to implement partial update in Java with the PATCH check out this post  – Transparent PATCH support in JAX-RS 2.0

3.2.3.1. Implementation

3.2.3.1.1. Full Update

Create or fully update resource implementation method

@PUT
@Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response putPodcastById(@PathParam("id") Long id, Podcast podcast)
		throws AppException {

	Podcast podcastById = podcastService.verifyPodcastExistenceById(id);

	if (podcastById == null) {
		// resource not existent yet, and should be created under the
		// specified URI
		Long createPodcastId = podcastService.createPodcast(podcast);
		return Response
				.status(Response.Status.CREATED)
				// 201
				.entity("A new podcast has been created AT THE LOCATION you specified")
				.header("Location",
						"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
								+ String.valueOf(createPodcastId)).build();
	} else {
		// resource is existent and a full update should occur
		podcastService.updateFullyPodcast(podcast);
		return Response
				.status(Response.Status.OK)
				// 200
				.entity("The podcast you specified has been fully updated created AT THE LOCATION you specified")
				.header("Location",
						"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
								+ String.valueOf(id)).build();
	}
}

Annotations

  • @PUT - indicates that the method responds to HTTP PUT requests
  • @Path("{id}") – identifies the URI path that the class method will serve requests for. The “id” value is an embedded variable making an URI path template. It is used in combination with the @PathParam variable.
    • @PathParam("id") – binds the value of a URI template parameter (“id”) to the resource method parameter. The value is URL decoded unless this is di sabled using the @Encoded annotation. A default value can be specified using the @DefaultValue annotation.
  • @Consumes({MediaType.APPLICATION_JSON}) – defines the media type, the method accepts, in this case "application/json"
  • @Produces({MediaType.TEXT_HTML}) – defines the media type) that the method can produce, in this case “text/html”.

will be a html document containing different messages and stati depending on what action has been taken

Respsonse

  • on creation
    • on success: 201 Created and in the Location header the specified location where the resource was created
    • on error: 400 Bad request if the minimum required properties are not provided for insertion
  • on full update
    • on success: 200 OK
    • on error: 400 Bad Request if not all properties are provided

3.2.3.1.2. Partial Update

Partial Update

//PARTIAL update
@POST
@Path("{id}")	
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response partialUpdatePodcast(@PathParam("id") Long id, Podcast podcast) throws AppException {
	podcast.setId(id);
	podcastService.updatePartiallyPodcast(podcast);
	return Response.status(Response.Status.OK)// 200
			.entity("The podcast you specified has been successfully updated")
			.build();	
}

Annotations

  • @POST – indicates that the method responds to HTTP POST requests
  • @Path("{id}") – identifies the URI path that the class method will serve requests for. The “id” value is an embedded variable making an URI path template. It is used in combination with the @PathParam variable.
    • @PathParam("id") – binds the value of a URI template parameter (“id”) to the resource method parameter. The value is URL decoded unless this is di sabled using the @Encoded annotation. A default value can be specified using the @DefaultValue annotation.
  • @Consumes({MediaType.APPLICATION_JSON}) – defines the media type, the method accepts, in this case "application/json"
  • @Produces({MediaType.TEXT_HTML}) – defines the media type) that the method can produce, in this case "text/html".

Response

  • on success: 200 OK
  • on error: 404 Not Found, if there is no resource anymore available at the provided location

3.2.4. Delete podcast

3.2.4.1. Design

DescriptionURIHTTP method
HTTP Status response
 Removes all podcasts /podcasts/DELETE204 No content
 Removes podcast at the specified location /podcasts/{id}DELETE204 No content

 

3.2.4.2. Implementation

3.2.4.2.1. Delete all resources

Delete all resources

@DELETE
@Produces({ MediaType.TEXT_HTML })
public Response deletePodcasts() {
	podcastService.deletePodcasts();
	return Response.status(Response.Status.NO_CONTENT)// 204
			.entity("All podcasts have been successfully removed").build();
}

Annotations

  • @DELETE – indicates that the method responds to HTTP DELETE requests
  • @Produces({MediaType.TEXT_HTML}) – defines the media type that the method can produce, in this case “text/html”.

Response

  • The response will be a html document, with a status of 204 No content, indicating to the caller that the request has been fulfilled.

3.2.4.2.2. Delete one resource

Delete one resource

@DELETE
@Path("{id}")
@Produces({ MediaType.TEXT_HTML })
public Response deletePodcastById(@PathParam("id") Long id) {
	podcastService.deletePodcastById(id);
	return Response.status(Response.Status.NO_CONTENT)// 204
			.entity("Podcast successfully removed from database").build();
}

Annotations

  • @DELETE – indicates that the method responds to HTTP DELETE requests
  • @Path("{id}") – identifies the URI path that the class method will serve requests for. The “id” value is an embedded variable making an URI path template. It is used in combination with the @PathParam variable.
    • @PathParam("id") – binds the value of a URI template parameter (“id”) to the resource method parameter. The value is URL decoded unless this is di sabled using the @Encoded annotation. A default value can be specified using the @DefaultValue annotation.
  • @Produces({MediaType.TEXT_HTML}) – defines the media type that the method can produce, in this case “text/html”.

Response

  • on success: if the podcast is removed a 204 No Content success status is returned
  • on error: podcast is not available anymore and status of 404 Not found is returned

4. Logging

Every request’s path and the response’s entity will be logged when the logging level is set to DEBUG. It is developed like a wrapper, AOP-style functionality with the help of Jetty filters.

See my post How to log in Spring with SLF4J and Logback for more details on the matter.

5. Exception handling

In case of errros, I decided to response with unified error message structure.  Here’s an example how an error response might look like:

Example – error message response

{
   "status": 400,
   "code": 400,
   "message": "Provided data not sufficient for insertion",
   "link": "http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-with-jersey-and-spring",
   "developerMessage": "Please verify that the feed is properly generated/set"
}

Note: Stay tuned, because the following post will present more details about error handling in REST with Jersey.

6. Add CORS support on the server side

I extended the capabilities of the API developed for the tutorial to support Cross-Origing Resource Sharing (CORS) on the server side.

Please see my post How to add CORS support on the server side in Java with Jersey for more details on the matter.

7. Testing

7.1. Integration tests in Java

To test the application I will use the Jersey Client and execute requests against a running Jetty server with the application deployed on it. For that I will use the Maven Failsafe Plugin.

7.1.1. Configuration

7.1.1.1 Jersey client dependency

To build a Jersey client the jersey-client jar is required in the classpath. With Maven you can add it as a dependency to the pom.xml file:

Jersey Client maven dependency

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>${jersey.version}</version>
    <scope>test</scope>
</dependency>

7.1.1.2. Failsafe plugin

The Failsafe Plugin is used during the integration-test and verify phases of the build lifecycle to execute the integration tests of the application. The Failsafe Plugin will not fail the build during the integration-test phase thus enabling the post-integration-test phase to execute.
To use the Failsafe Plugin, you need to add the following configuration to your pom.xml

Maven Failsafe Plugin configuration

<plugins>
	[...]
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.16</version>
        <executions>
            <execution>
                <id>integration-test</id>
                <goals>
                    <goal>integration-test</goal>
                </goals>
            </execution>
            <execution>
                <id>verify</id>
                <goals>
                    <goal>verify</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
	[...]
</plugins>

7.1.1.2. Jetty Maven Plugin

The integration tests will be executed against a running jetty server, that will be started only for the execution of the tests. For that you have to configure the following executionin the jetty-maven-plugin:

Jetty Maven Plugin configuration for integration tests

<plugins>
	<plugin>
		<groupId>org.eclipse.jetty</groupId>
		<artifactId>jetty-maven-plugin</artifactId>
		<version>${jetty.version}</version>
		<configuration>
			<jettyConfig>${project.basedir}/src/main/resources/config/jetty9.xml</jettyConfig>
			<stopKey>STOP</stopKey>
			<stopPort>9999</stopPort>
			<stopWait>5</stopWait>
			<scanIntervalSeconds>5</scanIntervalSeconds>
		[...]
		</configuration>
		<executions>
			<execution>
				<id>start-jetty</id>
				<phase>pre-integration-test</phase>
				<goals>
					<!-- stop any previous instance to free up the port -->
					<goal>stop</goal>				
					<goal>run-exploded</goal>
				</goals>
				<configuration>
					<scanIntervalSeconds>0</scanIntervalSeconds>
					<daemon>true</daemon>
				</configuration>
			</execution>
			<execution>
				<id>stop-jetty</id>
				<phase>post-integration-test</phase>
				<goals>
					<goal>stop</goal>
				</goals>
			</execution>
		</executions>
	</plugin>
	[...]
</plugins>

Note: In the pre-integration-test phase the Jetty server will be started, after stopping any running instance to free up the port, and in the post-integration-phase it will be stopped. The scanIntervalSeconds has to be set to 0, and daemon to true.

Code alert: Find the complete pom.xml file on GitHub

7.1.2. Build the integration tests

I am using JUnit as the testing framework. By default, the Failsafe Plugin will automatically include all test classes with the following wildcard patterns:

  • "**/IT*.java" – includes all of its subdirectories and all java filenames that start with “IT”.
  • "**/*IT.java" – includes all of its subdirectories and all java filenames that end with “IT”.
  • "**/*ITCase.java" – includes all of its subdirectories and all java filenames that end with “ITCase”.

I have created a single test class – RestDemoServiceIT – that will test the read (GET) methods, but the procedure should be the same for all the other:

public class RestDemoServiceIT {

	[....]
	@Test
	public void testGetPodcast() throws JsonGenerationException,
			JsonMappingException, IOException {

		ClientConfig clientConfig = new ClientConfig();
		clientConfig.register(JacksonFeature.class);

		Client client = ClientBuilder.newClient(clientConfig);

		WebTarget webTarget = client
				.target("http://localhost:8888/demo-rest-jersey-spring/podcasts/2");

		Builder request = webTarget.request(MediaType.APPLICATION_JSON);

		Response response = request.get();
		Assert.assertTrue(response.getStatus() == 200);

		Podcast podcast = response.readEntity(Podcast.class);

		ObjectMapper mapper = new ObjectMapper();
		System.out
				.print("Received podcast from database *************************** "
						+ mapper.writerWithDefaultPrettyPrinter()
								.writeValueAsString(podcast));

	}
}

Note:

  • I had to register the JacksonFeature for the client too so that I can marshall the podcast response in JSON format – response.readEntity(Podcast.class)
  • I am testing against a running Jetty on port 8888 – I will show you in the next section how to start Jetty on a desired port
  • I am expecting a 200 status for my request
  • With the help org.codehaus.jackson.map.ObjectMapper I am displaying the JSON response pretty formatted

7.1.3. Running the integration tests

The Failsafe Plugin can be invoked by calling the verify phase of the build lifecycle.

Maven command to invoke the integration tests

mvn verify

To start jetty on port 8888 you need to set the jetty.port property to 8888. In Eclipse I use the following configuration:

Run integration tests from Eclipse

Run integration tests from Eclipse

 

7.2. Integration tests with SoapUI

Recently I’ve rediscovered SoapUI after using it heavily for testing SOAP based web services. With the recent versions (at the time of writing latest is 5.0.0) it offers pretty good functionality to test REST based web services, and coming versions should improve on this. So unless you develop your own framework/infrastructure to test REST services, why not give it a try to SoapUI. I did, I was satisfied with the results so far and I’ve decided to do a video tutorial, that you can now find on YouTube on our channel:

8. Versioning

There are three major possibilities

  1. URL:  “/v1/podcasts/{id}”
  2. Accept/Content-type header: application/json; version=1

Because I am a developer and not a RESTafarian yet I would do the URL option. All I would have to do on the implementation side for this example, would be to modify the @Path‘s value annotation on the PodcastResource class from to

Versioning in the path

@Component
@Path("/v1/podcasts")
public class PodcastResource {...}

Of course on a production application, you wouldn’t want every resource class preprefixed with the version number,  you’d want the version somehow treated through a filter in a AOP manner. Maybe something like this will come in a following post…

Here are some great resources from people that understand better the matter:

9. Summary

Well, that’s it. I have to congratulate you, if you’ve come so far, but I hope you could learn something from this tutorial about REST, like designing a REST API, implementing a REST API in Java, testing a REST API and much more. If you did, I’d be very grateful if you helped it spread by leaving a comment or sharing it on Twitter, Google+ or Facebook. Thank you! Don’t forget also to check out Podcastpedia.org – you’ll find for sure interesting podcasts and episodes. We are grateful for your support.

If you liked this article, we would really appreciate a small contribution for our work! Donate now with Paypal.

10. Resources

10.1. Source Code

10.2. Web resources

  1. HTTP – Hypertext Transfer Protocol — HTTP/1.1 – RFC2616
  2. rfc5789 – PATCH Method for HTTP
  3. Jersey User Guide
  4. HTTP Status Code Definitions
  5. REST – http://en.wikipedia.org/wiki/Representational_State_Transfer
  6. CRUD – http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
  7. Java API for RESTful Services (JAX-RS)
  8. Jersey – RESTful Web Services in Java
  9. HTTP PUT, PATCH or POST – Partial updates or full replacement?
  10. Transparent PATCH support in JAX-RS 2.0
  11. Maven Failsafe Plugin
  12. Maven Failsafe Plugin Usage
  13. SoapUI 5.0 released today!
  14. SoapUI – Using Script Assertions
  15. [Video] REST+JSON API Design – Best Practices for Developers
  16. [Video] RESTful API Design – Second Edition
  17. Law of Demeter

10.3. Codingpedia related resources

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

2 Responses to "Tutorial – REST API design and implementation in Java with Jersey and Spring"

  1. Jamshid says:

    Thank you for this great tutorial!

    Question?
    are there any specific reasons not to use Spring’s ability to expose resources in a RESTful way with annotated Controllers?

  2. Adrian says:

    Hi Jamshid,

    Thanks. No special reason. I just wanted to know how the Jersey implementation looks like as I had to use it in some projects at work, and honestly feels “more natural” to me than the Spring implementation, which I also use in the Podcastpedia.org project…

    Cheers,
    Adrian

Leave a Reply


seven × = 28



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close