Jersey framework is a JAX-RS Reference Implementation. Jersey
provides it’s own API that extend the JAX-RS toolkit with additional features and utilities to further simplify
RESTful service and client development. Jersey also exposes numerous extension SPIs so that developers may extend
Jersey to best suit their needs.
Introduction
This post is a third in a series on
REST . In this post we will combine
the power of Spring
and JAX-RS
(Jersey) to build a REST API.
We will setup Spring to forward all requests to the Jersey Servlet for processing.
Project Structure
At the end of this guide our folder structure will look similar to the following:
.
|__src/
| |__main/
| | |__java/
| | | |__com/
| | | | |__juliuskrah
| | | | | |__Application.java
| | | | | |__Resource.java
| | | | | |__ResourceService.java
|__pom.xml
Prerequisites
To follow along this guide, your development system should have the following applications installed:
Creating Project Template
Head over to the Spring Initializr website to generate a Spring project template:
Spring Initializr
Select Jersey (JAX-RS)
and DevTools
as dependencies and generate the project. DevTools
is a handy tool to have
during development as it offers live reload when code changes.
Download and extract the template and let’s get to work .
Building Resources
Before we proceed, run the generated project to ensure it works:
mvnw clean spring-boot:run # Windows
./mvnw clean spring-boot:run # Linux, Mac
If everything goes well, Tomcat
should be started on port 8080
and wait for http
requests.
Setting up dependencies
Remove the spring-boot-starter-web
dependency from the pom.xml
. Refer to issue
3132
of Spring-Boot
for more insight on this bug:
file:
pom.xml
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-->
Building Resources
We will create a POJO
to represent our REST resource.
file:
src/main/java/com/juliuskrah/Resource.java
@XmlRootElement
public class Resource {
private Long id ;
private String description ;
private LocalDateTime createdTime ;
private LocalDateTime modifiedTime ;
// Default constructor
// All Args constructor
// Getters and Setters omitted for brevity
}
The next thing is to wire up a ResourceService
to expose some endpoints.
file:
src/main/java/com/juliuskrah/ResourceService.java
@Path ( "/api/v1.0/resources" )
@Produces ({ MediaType . APPLICATION_JSON , MediaType . APPLICATION_XML })
public class ResourceService {
private static List < Resource > resources = null ;
static {
resources = new ArrayList <>();
resources . add ( new Resource ( 1L , "Resource One" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 2L , "Resource Two" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 3L , "Resource Three" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 4L , "Resource Four" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 5L , "Resource Five" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 6L , "Resource Six" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 7L , "Resource Seven" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 8L , "Resource Eight" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 9L , "Resource Nine" , LocalDateTime . now (), null ));
resources . add ( new Resource ( 10L , "Resource Ten" , LocalDateTime . now (), null ));
}
/**
* GET /api/v1.0/resources : get all resources.
*
* @return the {@code List<Resource>} of resources with status code 200 (OK)
*/
@GET
public List < Resource > getResources () {
return resources ;
}
...
}
For a production ready application, you will normally connect to a database. For the purpose of this tutorial,
we will use a static
field to initialize our list.
In the class with the main
method; also annotated with @SpringBootApplication
,
add the following @Bean
of type ResourceConfig
in which you register all the endpoints:
file:
src/main/java/com/juliuskrah/Application.java
...
@Bean
public ResourceConfig jerseyConfig () {
ResourceConfig config = new ResourceConfig ();
config . register ( ResourceService . class );
return config ;
}
One more thing to wire Jersey
to Spring
, annotate your ResourceService
class with @Component
to
make it a Spring managed bean.
@Component
public class ResourceService { ... }
Start the server by running the following command:
mvnw clean spring-boot:run # Windows
./mvnw clean spring-boot:run # Linux, Mac
With the Tomcat server up and running, open another shell window and execute the following cURL
command:
> curl -i -H "Accept: application/json" http://localhost:8080/api/v1.0/resources
HTTP/1.1 200 OK
Content-Type: application/json
content-length: 991
connection: keep-alive
[
{
"id" : 1,
"description" : "Resource One" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 2,
"description" : "Resource Two" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 3,
"description" : "Resource Three" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 4,
"description" : "Resource Four" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 5,
"description" : "Resource Five" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 6,
"description" : "Resource Six" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 7,
"description" : "Resource Seven" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 8,
"description" : "Resource Eight" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 9,
"description" : "Resource Nine" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
} ,
{
"id" : 10,
"description" : "Resource Ten" ,
"createdTime" : "2017-07-13T22:36:28.384" ,
"modifiedTime" : null
}
]
We can request for the same resource as XML
representation:
> curl -i -H "Accept: application/xml" http://localhost:8080/api/v1.0/resources
HTTP/1.1 200 OK
Content-Type: application/xml
content-length: 1288
connection: keep-alive
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource One</description>
<id> 1</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Two</description>
<id> 2</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Three</description>
<id> 3</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Four</description>
<id> 4</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Five</description>
<id> 5</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Six</description>
<id> 6</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Seven</description>
<id> 7</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Eight</description>
<id> 8</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Nine</description>
<id> 9</id>
</resource>
<resource>
<createdTime> 2017-07-13T22:36:28.384</createdTime>
<description> Resource Ten</description>
<id> 10</id>
</resource>
</resources>
Let us write the REST operation for getting a specific resource /api/v1.0/resources/{id}
:
file:
src/main/java/com/juliuskrah/ResourceService.java
...
/**
* GET /api/v1.0/resources/:id : get the resource specified by the identifier.
*
* @param id the id to the resource being looked up
* @return the {@code Resource} with status 200 (OK) and body or status 404 (NOT FOUND)
*/
@GET
@Path ( "{id: [0-9]+}" )
public Resource getResource ( @PathParam ( "id" ) Long id ) {
Resource resource = new Resource ( id , null , null , null );
int index = Collections . binarySearch ( resources , resource , Comparator . comparing ( Resource: : getId ));
if ( index >= 0 )
return resources . get ( index );
else
throw new WebApplicationException ( Response . Status . NOT_FOUND );
}
...
The @Path
annotation takes a variable (denoted by {
and }
) passed by the client, which is interpreted by Jersey
in the @PathParam
and cast to a Long
as id
. The : [0-9]+
is a regular expression which constraint the client
to use only positive whole numbers otherwise return 404
to the client.
If the client passes the path parameter in the format the server accepts, the id
of the resource will be searched
from within the static
resources field. If it exist return the response to the client or else return a 404
.
Test this resource by running:
> curl -i -H "Accept: application/json" http://localhost:8080/api/v1.0/resources/1
HTTP/1.1 200 OK
Content-Type: application/json
content-length: 96
connection: keep-alive
{
"id" :1,
"description" : "Resource One" ,
"createdTime" : "2017-07-14T23:55:18.76" ,
"modifiedTime" : null
}
Now let us write our POST
method that creates a new resource:
file:
src/main/java/com/juliuskrah/ResourceService.java
...
/**
* POST /api/v1.0/resources : creates a new resource.
*
* @param resource the resource being sent by the client as payload
* @return the {@code Resource} with status 201 (CREATED) and no -content or status
* 400 (BAD REQUEST) if the resource does not contain an Id or status
* 409 (CONFLICT) if the resource being created already exists in the list
*/
@POST
public Response createResource ( Resource resource ) {
if ( Objects . isNull ( resource . getId ()))
throw new WebApplicationException ( Response . Status . BAD_REQUEST );
int index = Collections . binarySearch ( resources , resource , Comparator . comparing ( Resource: : getId ));
if ( index < 0 ) {
resource . setCreatedTime ( LocalDateTime . now ());
resources . add ( resource );
return Response
. status ( Response . Status . CREATED )
. location ( URI . create ( String . format ( "/api/v1.0/resources/%s" , resource . getId ())))
. build ();
} else
throw new WebApplicationException ( Response . Status . CONFLICT );
}
What is happening in the above snippet is a POST
request that returns a 201
status code and a Location
header
with the location of the newly created resource.
If the resource being created already exists on the server an error code of 409
is returned by the server.
> curl -i -X POST -H "Content-Type: application/json" -d "{ \" id \" : 87, \" description \" : \" Resource Eighty-Seven \" }" http://localhost:8080/api/v1.0/resources
HTTP/1.1 201 Created
Location: http://localhost:8080/api/v1.0/resources/87
content-length: 0
connection: keep-alive
For those not using Windows, you should omit the escape \
.
The remaining two methods of our webservice is shown below:
file:
src/main/java/com/juliuskrah/ResourceService.java
...
/**
* PUT /api/v1.0/resources/:id : update a resource identified by the given id
*
* @param id the identifier of the resource to be updated
* @param resource the resource that contains the update
* @return the {@code Resource} with a status code 204 (NO CONTENT) or status code
* 404 (NOT FOUND) when the resource being updated cannot be found
*/
@PUT
@Path ( "{id: [0-9]+}" )
public Response updateResource ( @PathParam ( "id" ) Long id , Resource resource ) {
resource . setId ( id );
int index = Collections . binarySearch ( resources , resource , Comparator . comparing ( Resource: : getId ));
if ( index >= 0 ){
Resource updatedResource = resources . get ( index );
updatedResource . setModifiedTime ( LocalDateTime . now ());
updatedResource . setDescription ( resource . getDescription ());
resources . set ( index , updatedResource );
return Response
. status ( Response . Status . NO_CONTENT )
. build ();
} else
throw new WebApplicationException ( Response . Status . NOT_FOUND );
}
/**
* DELETE /api/v1.0/resources/:id : delete a resource identified by the given id
*
* @param id the identifier of the resource to be deleted
* @return the {@code Response} with a status code of 204 (NO CONTENT) or status code
* 404 (NOT FOUND) when there is no resource with the given identifier
*/
@DELETE
@Path ( "{id: [0-9]+}" )
public Response deleteResource ( @PathParam ( "id" ) Long id ) {
Resource resource = new Resource ( id , null , null , null );
int index = Collections . binarySearch ( resources , resource , Comparator . comparing ( Resource: : getId ));
if ( index >= 0 ) {
resources . remove ( index );
return Response
. status ( Response . Status . NO_CONTENT )
. build ();
} else
throw new WebApplicationException ( Response . Status . NOT_FOUND );
}
Test the PUT
endpoint with the following command:
> curl -i -X PUT -H "Content-Type: application/json" -d "{ \" description \" : \" Resource One Modified \" }" http://localhost:8080/api/v1.0/resources/1
HTTP/1.1 204 No Content
content-length: 0
connection: keep-alive
Test DELETE
endpoint with the following command:
> curl -i -X DELETE http://localhost:8080/api/v1.0/resources/1
HTTP/1.1 204 No Content
content-length: 0
connection: keep-alive
Conclusion
In this post we built up on our previous post by wiring up the best of Spring and Jersey in the same application. In the next post we will learn how to secure a RESTful
Web Service using JWT.
As usual you can find the full example to this guide in the github repository .
Until the next post, keep doing cool things .
If you would like to support my work, consider