JAX-RS and Jersey
JAX-RS, is the “Java™ API for RESTful Web Services (JAX-RS)”. Documented in JSR 311 & JSR 339 it specifies a bunch of interfaces and annotations that can be used to define RESTful Webservices in java.
Jersey is a reference implementation of JAX-RS Spec. In addition to implementing the JSR 331 and 339, JAX-RS provides its own extensions to the JAX-RS API. Although Jersey is a good starting point, any other JAX-RS implementation can be used in its place as well.
Also JAX-RS webservices can be created standalone, i.e., without Spring. However, using Spring along with Jersey makes it easier to build and scale the webservices due to the ability to use DI, Data access and data representation (e.g., JSON/XML Marshalling), Security, Enterprise Integration and other powerful features of Spring.
Create a new Jersey Webservice with Spring
In this article we will be creating RESTFul webservices with Jersey & Spring. We will be using the Spring Tool Suite (STS) for this purpose and in the screenshots. However we can also use maven on the command-line and eclipse and achieve the same results. In this example we will create a web-service that will provide a REST API to manage a collection of films. The client can retrieve the list of films in the collection along with their year of release and other details. add films to it, modify individual films’ attributes as well as delete films from it.
To get started, download STS from its download page. Choose the appropriate platform and architecture and download. Extract and launch the executable named “STS” to start Spring Tools Suite.
Before you continue following the steps outline below make sure you have an active internet connection. This is because STS/Maven download the project dependencies automatically from the internet as needed.
Use File -> New Spring Starter Project
to create a new Spring starter project.

In the dialog that opens up enter the artifact ID, group ID and package name as desired and leave the rest to the defaults as shown in the screenshot.

Click Next and in the “New Spring Starter Dependencies” screen select Jersey (within Web).

Click Finish for STS to create the project. Wait for the background process to finish downloading and setting up the project. (Progress Tab shows the status)
The STS creates the project directory structure and downloads all the dependencies. As we can see it is a typical maven project structure with all the java source files located in src/main/java
, the unit tests and automated tests in src/test/java
and a pom.xml
in the project root directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javagists</groupId> <artifactId>JerseyFilms</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>JerseyFilms</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
The project has a parent tag pointing to the latest stable release of spring. It has the spring-boot-starter-jersey
dependency to ensure Jersey and its dependencies are pulled in. Finally the spring-boot-maven-plugin
adds the maven build capabilities to the project. A quick look at the project properties under dependencies confirms we have the necessary dependencies added.
All the classes we will create for the rest of the article will be in src/main/java
within the com.javagists.jerseyfilms
package.
Anatomy of a JAX-RS Webservice
Generally a JAX-RS webservice has 1 or more Root resource classes, a JerseyConfiguration class, an (optional) ExceptionMapper class and the Spring boot Application class in addition to domain classes which implement the business logic (model, persistence, etc.,.).
The Root Resource Class(es)
As we saw earlier, providing a REST API is essentially about making resources accessible via well-defined URI (endpoints) and implementing the Uniform interface on these. In JAX-RS, this is accomplished by creating a Root resource class which is a Java Class that uses JAX-RS annotations like @Path to implement a corresponding Web Resource. They are essentially POJOs that have a class-level annotation or a method level annotation. We will be shortly looking at these annotations and what they mean.
First let’s create a FilmController
class that will be our Root Resource Class. We’ll create a new package called controller
and create FilmController
inside this package. The source code for FilmController.java
is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package com.javagists.jerseyfilms.controller; import java.net.URI; import java.util.Collection; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.javagists.jerseyfilms.model.Film; import com.javagists.jerseyfilms.service.FilmService; @Component @Path("/films") public class FilmController { @Autowired FilmService fs; // API End-point to get a list of all films in the database @GET @Produces("application/json") public Collection<Film> films() { return fs.getAllFilms(); } // API End-point to get a specific film by id @GET @Path("/{id}") @Produces("application/json") public Film getFilm(@PathParam("id") String id) { return fs.getFilm(id); } // API End-point to add a new film to database @POST @Consumes("application/json") public Response add(Film film, @Context UriInfo info) { fs.addFilm(film); return Response.created(URI.create( info.getAbsolutePath().toString()+"/"+film.getId() )).build(); } } |
The class has a @Path annotation which indicates the base-path of the web resource. In this case the web resource implemented by FilmController
class will be accessible at http://localhost:8080/films
. The films() method has a @GET tag, meaning whenever a GET request is received at this end-point this method will be invoked. Finally it has an @Produces tag which indicates the type of response produced, in this case, “application/json”.
Notice how the method returns has a return type of Collection
— the conversion into JSON is handled automatically by the platform (more specifically, by the Jackson library that was included as a dependency when we created the Spring Starter app with Jersey).
Next is the add method. The @POST tag indicates this is the handler for POST requests, the @Consumes tag indicates the type of input data. The @Path tag has "/{id}"
which is to indicate that the path to this resource is obtained by adding an id suffix to the base-path of the class – for example, http://localhost:8080/films/2
. The value of this suffix is set to the id
parameter of the function by decorating it with @PathParam{"id"}
. The method returns a success response code of 201, which is the standard code for successful resource creation, along with a link to the newly created resource. In real-life scenario this method will check for id collision or other errors and handle or report error – we skip that for sake of simplicity now. Further down in the article we will look at how we can handle errors.
Helper Classes
The JerseyConfiguration class registers the Root resource classes. This is an implementation detail of Jersey. Below is the source code for this file. It just registers the one Root Resource class that we have created.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.javagists.jerseyfilms; import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; import com.javagists.jerseyfilms.controller.FilmController; @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(FilmController.class); } } |
The JerseyFilmsApplication has the @SpringBootApplication decorator – it bootstraps and starts our Spring Boot App. This is generated by STS and can be used to configure application level properties such as the base-path for the application.
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.javagists.jerseyfilms; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class JerseyFilmsApplication { public static void main(String[] args) { SpringApplication.run(JerseyFilmsApplication.class, args); } } |
Domain classes
The FilmController
class we created above depends on the FilmService
and Film
Classes. We create the service
and model
packages to house these classes.
The Film
Class is our data model and encapsulates the information about a Film.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package com.javagists.jerseyfilms.model; public class Film { private String id; private String name; private String year; private Genre genre; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getYear() { return year; } public void setYear(String year) { this.year = year; } public Genre getGenre() { return genre; } public void setGenre(Genre genre) { this.genre = genre; } } |
Genre
is an enum defined in Genre.java
1 2 3 4 5 |
package com.javagists.jerseyfilms.model; public enum Genre { ACTION, ADVENTURE, BIOGRAPHY, COMEDY, CRIME, DRAMA, HISTORICAL, HORROR, MUSICAL, SCIFI, WAR, WESTERN ; } |
The FilmService
class maintains the collection of films.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.javagists.jerseyfilms.service; import java.util.Collection; import java.util.Collections; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.springframework.stereotype.Service; import com.javagists.jerseyfilms.model.Film; @Service public class FilmService { private final ConcurrentMap<String, Film> db; public FilmService() { this.db = new ConcurrentHashMap<>(); } // Get all the films stored in the database public Collection<Film> getAllFilms() { Collection<Film> all = this.db.values(); if (all.isEmpty()) { return Collections.emptyList(); } else { return all; } } // Add a film to database public void addFilm(Film f) { if(f.getId() == null) { f.setId(String.valueOf(this.db.size()+1)); } this.db.put(f.getId(), f); } // Get a film by id public Film getFilm(String id) { return this.db.get(id); } } |
In our example, we initialise the collection to an empty list. However in a real-life scenario we could connect to a database or other source for this information (leveraging Spring capabilities to do so robustly). The FilmService
class provides methods to add a film to the list and to get the list of films.
Testing our REST API
Now that our webservice is ready we can run our application by right-clicking on our project, selecting “Run As…” -> “Spring Boot App”. The embedded tomcat server will be launched and the webservice will be deployed. Now we can access the web-service by pointing our browser at http://localhost:8080/films
or by using Postman.
When a GET request is sent the URL http://localhost:8080/films
for the first time, the server responds with the empty list. This is because the collection is initialised empty. Sending a POST request to the same URL with a JSON object having the details of a film adds it to the database. After the POST request is successful (indicated by a 201 response code), a subsequent GET request returns a list with all the films added. Multiple such POST requests can be made to add more films to the database. Below is some sample JSON data for testing the webservice.
As we have been using Jersey framework. Let’s use the movies also from Jersey Films. Makes it a bit more interesting 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// POST (without ID) { "name": "Erin Brockovich", "year": "2000", "genre": "DRAMA" } // POST (with ID) { "id": 2, "name": "Pulp Fiction", "year": "1994", "genre": "CRIME" } // POST (without ID) { "name": "Man on the Moon", "year": "1999", "genre": "COMEDY" } // PUT { "id": 3, "name": "Man on the Moon", "year": "1999", "genre": "BIOGRAPHY" } |
One more thing to notice: When the server returns the list of films, it returns the genre
attribute as a string although it stores it internally as an enum. This is done to represent the object using JSON. The webservice also accepts string for this field and internally converts it into the enum. Thus, client and server exchange a representation of the resource — not the resource itself.
Implementing PUT and DELETE operations
Now that we can add new films get the updated list, let’s add the ability to modify films in the database. We extend the FilmService
& FilmController
with methods to update and remove films. The updated source for the FilmService
class is below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
package com.javagists.jerseyfilms.service; import java.util.Collection; import java.util.Collections; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.springframework.stereotype.Service; import com.javagists.jerseyfilms.model.Film; @Service public class FilmService { private final ConcurrentMap<String, Film> db; public FilmService() { this.db = new ConcurrentHashMap<>(); } // Get all the films stored in the database public Collection<Film> getAllFilms() { Collection<Film> all = this.db.values(); if (all.isEmpty()) { return Collections.emptyList(); } else { return all; } } // Add a film to database public void addFilm(Film f) { if(f.getId() == null) { f.setId(String.valueOf(this.db.size()+1)); } this.db.put(f.getId(), f); } // Get a film by id public Film getFilm(String id) { return this.db.get(id); } // Modify a film attributes public Film updateFilm(String id, Film f) { if(!this.db.containsKey(id)) { throw new IllegalArgumentException("Invalid Film or Film does not exist!"); } if((f.getId() == null) || (id != f.getId())) { f.setId(id); } return this.db.put(f.getId(), f); } // Delete a film from database public void removeFilm(String id) { if(!this.db.containsKey(id)) { throw new IllegalArgumentException("Invalid Film or Film does not exist!"); } this.db.remove(id); } } |
We have 2 new methods – one to update an existing entry and the other to delete. Both these methods throw an IllegalArgumentException if the id cannot be found in the database.
The updated source for the FilmController
class is below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
package com.javagists.jerseyfilms.controller; import java.net.URI; import java.util.Collection; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; 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.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.javagists.jerseyfilms.model.Film; import com.javagists.jerseyfilms.service.FilmService; @Component @Path("/films") public class FilmController { @Autowired FilmService fs; // API End-point to get a list of all films in the database @GET @Produces("application/json") public Collection<Film> films() { return fs.getAllFilms(); } // API End-point to get a specific film by id @GET @Path("/{id}") @Produces("application/json") public Film getFilm(@PathParam("id") String id) { return fs.getFilm(id); } // API End-point to add a new film to database @POST @Consumes("application/json") public Response add(Film film, @Context UriInfo info) { fs.addFilm(film); return Response.created(URI.create( info.getAbsolutePath().toString()+"/"+film.getId() )).build(); } //API End-point to modify a film @PUT @Path("/{id}") @Consumes("application/json") @Produces("application/json") public Film update(@PathParam("id") String id, Film film) { fs.updateFilm(id, film); return film; } // API End-point to delete a film @DELETE @Path("/{id}") public Response delete(@PathParam("id") String id) { fs.removeFilm(id); return Response.ok(id).build(); } } |
We have added 2 new methods. The first one is update() – the @PUT decorator indicates that this method handles PUT requests. The @Path & @Consumes decorations specify, respectively, the resource path and data-type accepted. This method delegates to the fs.updateFilm method and returns the updated Film object back.
Next we have the delete method having the @DELETE decorator which removes the film and returns OK if that is successful.
Handling Exceptions
To handle the exceptions that the methods of FilmService class may throw we create a generic ExceptionMapper class and register it as a provider. The source code for this class is below. This class simply returns a server error response built with the message from the exception.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.javagists.jerseyfilms; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider public class FilmExceptionMapper implements ExceptionMapper<Throwable> { @Override public Response toResponse(Throwable e) { return Response.serverError().entity(e.getMessage()).build(); } } |
Results
Now the REST API is complete and supports all the CRUD operations. We can test it using Postman and the test data given above.
Final remarks
As you can see from this post, it is very easy to create RESTFul web-services in Java using STS and Jersey. Combining Jersey which is the reference JAX-RS implementation with Spring allows us to leverage both the frameworks and focus development efforts purely on the business logic leaving out the rest of the details to be handled by the frameworks.
Hope you enjoyed learning this and given the popularity of REST APIs, I hope you can apply it in your life. You can find all the code used in this tutorial at GitHub.
What would be right place to define an additional spring bean. If I define that as @component. Would that be enough?
@Component should be fine. And use @Autowire to inject the bean.
I followed your tutorial and sometimes I get method not found for some of my methods? Can you help me with the errror?
It is hard to tell what issue you have. But a tip can be to try having all the parameters as String. And see if that works.
Can I create spring projects without the STS? It seems too complicated to me.
yes, you can create. Simply use maven dependencies. This just makes it a little easier when you are working with an IDE.