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.

pom.xml <?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> 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.

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

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

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

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

package com.javagists.jerseyfilms.model; public enum Genre { ACTION, ADVENTURE, BIOGRAPHY, COMEDY, CRIME, DRAMA, HISTORICAL, HORROR, MUSICAL, SCIFI, WAR, WESTERN ; } 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.

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

// 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" } 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.

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

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

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

Share this: Facebook

LinkedIn

Twitter

Tumblr

Pinterest



Like this: Like Loading...