Luis Costa

Getting started with the Jersey Framework

Jersey is a framework that allows you to build RESTful applications with the Java language. It is based on the JAX-RS API but it also provides its own API with some other neat futures.

In a nutshell, it allows you to create a application that responds to common GET/PUT/POST/DELETE (etc) requests, for instance, a request to the URL http://somehost:3000/users/Joe could be a GET request to retrieve data from the user Joe and the application could return, for instance, a JSON object with the attributes of the user Joe, like the full name, age, city, etc.

Why am I writing about this?

There are already lot’s of “Hello, World!” tutorials about Jersey, but I feel like they are all so overwhelming and some are getting quite old. I’m not saying they are bad tutorials, but I wanted something simple and straight to the point and which covers the main aspects of the framework. Hopefully, I’ll achieve that with this blog post. If you’re interested in jersey, this should help you get started building your own applications.

What do I need to know before start using Jersey?

Besides knowing the basics of the Java language, you should also know the basics of the Maven build system. In this post, I’ll use Eclipse to configure the project, so knowing Maven is not 100% needed, but you should at least follow this tutorial to help you getting started. If you’re using Eclipse like me, then you should install the M2E plugin for eclipse and you won’t necessary need to know how to run maven commands from the command line.

Quickly building the “Hello, World!”

I’m going to assume you’re familiar with the command line. You can do all these things with the eclipse GUI and the M2E plugin, but honestly I prefer using the command line for these tasks. Open your terminal, cd into the your eclipse workplace where your projects are, and run:

$ mvn archetype:generate
$ Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 661: <press enter>
$ Choose a number: 6: <press enter>
$ Define value for property 'groupId': : yourdomain.jerseytest
$ Define value for property 'artifactId': : TryJersey
$ Define value for property 'version':  1.0-SNAPSHOT: : <press enter>
$ Define value for property 'package':  yourdomain.jerseytest: : <press enter>
# Press enter when the prompt shows the configuration properties to confirm.

You should have now a folder called TryJersey inside the eclipse workspace. Let’s import the project into Eclipse so we can start working. Go to File -> Import -> Existing Maven Projects and select the folder TryJersey.

The first thing one needs to do in order to use Jersey is to update the file pom.xml which contains the maven configuration, to include the dependencies needed. Double click on pom.xml and click on the tab pom.xml to manually edit the file, which should be at the right of the tab Effective POM. Let’s edit the pom.xml to include some needed dependencies. You can copy and paste this XML to your file, but be sure to update the versions of the tools.

<project xmlns="...">
  <modelVersion>4.0.0</modelVersion>

  <groupId>yourdomain.jerseytest</groupId>
  <artifactId>TryJersey</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencyManagement>
  <dependencies>
      <!-- The Jersey framework -->
      <dependency>
          <groupId>org.glassfish.jersey</groupId>
          <artifactId>jersey-bom</artifactId>
          <version>${jersey.version}</version>
          <type>pom</type>
          <scope>import</scope>
      </dependency>
  </dependencies>
  </dependencyManagement>
  <packaging>jar</packaging>

  <name>TryJersey</name>
  <url>http://maven.apache.org</url>

  <properties>
     <jersey.version>2.22</jersey.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>

  <!-- junit, a java testing framework -->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>

  <!-- Jersey's HTTP server -->
  <dependency>
     <groupId>org.glassfish.jersey.containers</groupId>
     <artifactId>jersey-container-grizzly2-http</artifactId>
    </dependency>

  <!-- JSON will be useful to build the HTTP responses -->
  <dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20141113</version>
  </dependency>

  <!-- Jersey's utilities. -->
  <dependency>
      <groupId>org.glassfish.jersey.media</groupId>
      <artifactId>jersey-media-moxy</artifactId>
  </dependency>

  <!-- The Jersey's testing framework -->
  <dependency>
      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
  </dependency>

  </dependencies>

  <build>
    <plugins>

     <!-- Plugin to execute your application from the cmd line -->
     <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <version>1.2.1</version>
     </plugin>
    </plugins>
  </build>
</project>


Everything is ready, let’s start coding! Today we’ll build a simple API/backend for a company that sells books. The API will be used so that, for example, the web developer of the company can create a web app which displays the books that the company sells. The API will receive HTTP GET, POST and DELETE requests, look up in an “invented” database for the book requested, and return the response in JSON format. For the sake of this example, let’s say that books can be categorized into novels, fiction, criminal and romances and that all books have an id, a name, an ISBN, price and an author name. 

So, our REST endpoints could consist of:

GET /book/:id returns a JSON object with every attribute of book with id :id

GET /book/{category}/all returns a JSON object with every book of the category {category}

GET /book/:id/name Returns the name of the book with id :id

GET /book/:id/isbn Returns the ISBN of the book with id :id

GET /book/:id/price Returns the price of the book with id :id

GET /book/:id/author Returns the author of the book with id :id

POST /book/new Creates a new book

DELETE /book/:id Deletes the book with id :id

The first thing we need to do is to create resources. Resources are java classes with methods annotated with @Path, @GET, @POST @DELETE and @PUT. Essentially, a resource defines a set of endpoints which your API will be able to respond to. In our case, we can clearly see that there will be a resource called BookResource along with @GET, @POST and @DELETE methods.

So let’s get right into it! Let’s create our BookResource and the Hello World method

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("book")
public class BookResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello_world() {
        return "Hello, World!";
    }

}

The @Path("book") annotation is saying that this resource will answer to requests with the URL http://somehost:someport/.../book/.... The @GET in the hello_world() method specifies that this method responds to a request to the root of this resource, i.e, to /book/. The @Produces specifies what kind of response this method is going to return. In this case, we’re returning simple text, but as we’ll see later, there are other types of return types.

Let’s now start our HTTP server and test out the URL http://localhost:8080/jerseytest/book/ and see if we get Hello, World! (use curl). First we need to build the main class and start the built-in Jersey’s HTTP server:

package yourdomain.jerseytest.TryJersey;

// imports ...

public class App
{
    /**
     * BASE_URI where the server will run
     */
    public static final URI BASE_URI =
          URI.create("http://localhost:8080/jerseytest");

    public static void main( String[] args )
    {
        try {

            // Init options for the server.
            // By saying PROVIDER_PACKAGES, we're telling Jersey to look
            // for the packages with the name
            // of the package of BookResource for resources.
            // That is, jersey will look for resource classes in
            // that package.
            // You could also simply pass in the package name as a string.
            Map<String, String> serverInitParams = new HashMap<String, String>();
            serverInitParams.put(ServerProperties.PROVIDER_PACKAGES,
                    BookResource.class.getPackage().getName());

            // Create the HTTP server
            final HttpServer server =
                GrizzlyWebContainerFactory
                .create(BASE_URI, ServletContainer.class, serverInitParams);

            // Add a thread that will run when the user terminates the server
            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

                public void run() {
                    System.out.println("Shutting down server");
                    server.shutdown();
                }
            }));

            System.out.println("Application started at URI " + BASE_URI);

            // Blocks the current thread (where the server is running)
            // until it is completed or the user terminates
            // the program
            Thread.currentThread().join();
        } catch(Exception e) {
            System.err.println(e.getMessage());
        }
    }
}

Now, if we want to test it out, we need to execute the HTTP Server. Open the command line, cd into the root directory of the project and run:

$ mvn clean package exec:java -Dexec.mainClass="yourdomain.jerseytest.TryJersey.App"

Now, open a new terminal and run the command:

$ curl http://localhost:8080/jerseytest/book
Hello, World!

If you don’t have curl installed, install it with:

$ apt-get install curl

You can also test it in your browser, by accessing the same Url.

Great! Let’s write now our first test. The test should assert that a request to localhost:port/book/ should return a 200 OK HTTP response with a response of Hello, World!.

In order to create a test, we need to create a test class that extends the JerseyTest class, from the Jersey testing framework, as follows:

package yourdomain.jerseytest.TryJersey.resources;
// imports ...

public class BookResourceTest extends JerseyTest{

    /**
     * Configure the resources that we want to test
     * @return a resource config with the classes we want to test.
     */
    @Override
    protected Application configure() {
        return new ResourceConfig(BookResource.class);
    }

    /**
     * A request to localhost:port/book should return a 200 OK response
     * and a "Hello, World!" response.
     */
    @Test
    public void shouldGetHelloWorld() {
        Response response = target("book/").request().get();

        assertEquals(response.getStatus(), 200);
        assertEquals("Hello, World!", response.readEntity(String.class));
    }

}

And now we run

$ mvn test

And the test should pass

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running yourdomain.jerseytest.TryJersey.resources.BookResourceTest
...
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.181 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Great, we successfully created a resource and tested that it returns the right responses. We now have the basis to create the endpoints of our API and how to test them.

Before proceeding, we need to actually have books in the store. Books will be represented by a domain class, Book. Let’s go ahead and create that class now.

package yourdomain.jerseytest.TryJersey.domain;

public class Book {

    private Genre genre;

    private int id;

    private String name;

    private double price;

    private String isbn;

    private String author;

    // a bunch of getters and setters

    public Book(Genre genre, int id, String name,
                double price, String isbn, String author) {
        this.genre = genre;
        this.id = id;
        this.name = name;
        this.price = price;
        this.isbn = isbn;
        this.author = author;
    }

    @Override
    public String toString() {
        return "[BOOK : " + this.getName() + ", AUTHOR: " + this.author +
                ", GENRE: " + this.genre + ", PRICE: " + this.price
                + ", ISBN: " + this.isbn + ", ID: " + this.id + "]";

}

The Genre is a simple enum class:

package yourdomain.jerseytest.TryJersey.domain;

public enum Genre {
    NOVEL,
    FICTION,
    CRIME,
    ROMANCE
}

For simplicity of our example, let’s simulate our database of books with a simple class, BooksPersistance:

package yourdomain.jerseytest.TryJersey.persistance;

import java.util.ArrayList;
import java.util.List;
import yourdomain.jerseytest.TryJersey.domain.Book;

public class BooksPersistance {

    public static List<Book> books = new ArrayList<Book>();

    public static void addBook(Book b) {
        books.add(b);
    }

    public static void deleteBook(int id) {
        books.remove(id);
    }

    public static int numberOfBooks() {
        return books.size();
    }

    public static void clearBooks() {
        books.clear();
    }
}

To take into account this new class, the App class must be changed. We’ll now use the BooksPersistance class to fill our simulated database with 25 books.

public class App
{

    // ...

    /**
     * Maximum price of a book
     */
    public static final double MAX_PRICE = 50.0; // Because why not!

    /**
     * Minimum price of a book
     */
    public static final double MIN_PRICE = 5.0; // No free books for you

    public static final Random RANDOM_NUMBER_GENERATOR = new Random();

    // ...

    public static void main( String[] args )
    {
        try {

            // ...

            createBooks(25);

            // ...

        } catch(Exception e) {
            System.err.println(e.getMessage());
        }
    }

  private static void createBooks(int numberOfBooks) {
      for ( int i = 0 ; i < numberOfBooks ; i++ ) {

          double bookPrice = createRandomBookPriceBetween(MIN_PRICE, MAX_PRICE);
          Book book = createRandomBookAtrrs(bookPrice, i);
          if (!(book == null)) {
              BooksPersistance.addBook(book);
          } else {
              System.err.println("Some error ocurred while creating a book");
              System.exit(-1);
          }
        }
    }

  private static double createRandomBookPriceBetween(double min, double max) {
      DecimalFormat decimalFormat = new DecimalFormat("#.##");
      double bookPrice = min + (max - min) * RANDOM_NUMBER_GENERATOR.nextDouble();
      bookPrice = Double.valueOf(decimalFormat.format(bookPrice));
      return bookPrice;
  }

    private static Book createRandomBookAtrrs( double bookPrice, int iterNumber) {
        int randNumber = RANDOM_NUMBER_GENERATOR.nextInt(4);
        switch (randNumber) {
            case 0:
                return new Book(Genre.NOVEL, iterNumber,
                        "BookName#"+iterNumber, bookPrice,
                        "isbn#id" + iterNumber, "Author#" + iterNumber);
            case 1:
                return new Book(Genre.FICTION, iterNumber,
                        "BookName#"+iterNumber, bookPrice,
                        "isbn#id" + iterNumber, "Author#" + iterNumber);
            case 2:
                return new Book(Genre.CRIME, iterNumber,
                        "BookName#"+iterNumber, bookPrice,
                        "isbn#id" + iterNumber, "Author#" + iterNumber);
            case 3:
                return new Book(Genre.ROMANCE, iterNumber,
                        "BookName#"+iterNumber, bookPrice,
                        "isbn#id" + iterNumber, "Author#" + iterNumber);
            default:
                return null;
        }
    }
}

Let’s now implement the endpoints for our API. One common operation will be to find a book given it’s id, so it is a good practice to write that method separately so we can reuse it in the remaining implementations of the endpoints:

// ...

public class BooksPersistance {

    // ...

    public static Book findBookById(int id) {
        Iterator<Book> iter = books.iterator();
        while (iter.hasNext()) {
            Book book = iter.next();
            if (book.getId() == id) {
                return book;
            }
        }
        return null;
    }
}

I will not show here the implementations of all the GET endpoints since they are all really similar. I’ll show here the implementations of the /book/{category}/all and /book/:id/. You should be able to create the remaining GET requests based on these ones pretty easily. You can find the rest of the implementations on my github repository.

For the /book/:id/:

// ...

@Path("book")
public class BookResource {

  // ...

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public String getBookById(@PathParam("id") int id) {

        Book book = BooksPersistance.findBookById(id);
        if (book != null) {
            JSONObject obj = new JSONObject();
            obj.put("id", book.getId());
            obj.put("name", book.getName());
            obj.put("author", book.getAuthor());
            obj.put("price", book.getPrice());
            obj.put("genre", book.getGenre());
            obj.put("isbn", book.getIsbn());
            return obj.toString(2);
        } else {
           return new JSONObject().toString(2);
        }
    }

    // ...
}

For the /book/{category}/all, we need a new method in the BooksPersistance class to get all the books in a given category:

public class BooksPersistance {

    // ...

    public static List<Book> getBooksByGenre(String genre) {
        List<Book> booksInGenre = new ArrayList<Book>();
        for (Book book : books) {
            if (book.getGenre().name().equalsIgnoreCase(genre)) {
                booksInGenre.add(book);
            }
        }
        return booksInGenre;
    }
}

And the implementation of the endpoint is:

// ...

@Path("book")
public class BookResource {

  // ...

    @GET
    @Path("/{category}/all")
    @Produces(MediaType.APPLICATION_JSON)
    public String getBooksInCategory(@PathParam("category") String category) {
        List<Book> booksInGenre = BooksPersistance.getBooksByGenre(category);
        JSONObject result = new JSONObject();
        for (Book book : booksInGenre) {
            JSONObject obj = new JSONObject();
            obj.put("id", book.getId());
            obj.put("name", book.getName());
            obj.put("author", book.getAuthor());
            obj.put("price", book.getPrice());
            obj.put("genre", book.getGenre());
            obj.put("isbn", book.getIsbn());
            result.append(category, obj);
        }
        return result.toString(2);
    }

    // ...
}

Some considerations: the MediaType expresses the kind of response each method is returning. In both methods, the response is of type JSON or {application/json}. Each method can receive additional parameters. Those parameters can be bound to the function parameters via the annotation @PathParam. Usually they correspond to the part that is surrounded by curly braces in the @Path annotation of the method. For instance, in the method getBooksInCategory, the @Path has {category} in it’s string, so we denote the function parameter category with @PathParam.

The POST endpoint implementation is as follows:

  // ...

    @POST
    @Path("/new")
    public Response createNewBook(
                                  @FormParam("name") String name,
                                  @FormParam("genre") String genre,
                                  @FormParam("price") double price,
                                  @FormParam("isbn") String isbn,
                                  @FormParam("author") String author) {
        int newId = BooksPersistance.numberOfBooks();
        Genre newGenre;
        if (genre.equalsIgnoreCase(Genre.CRIME.name())) {
            newGenre = Genre.CRIME;
        } else if (genre.equalsIgnoreCase(Genre.NOVEL.name())) {
            newGenre = Genre.NOVEL;
        } else if (genre.equalsIgnoreCase(Genre.FICTION.name())) {
            newGenre = Genre.FICTION;
        } else if (genre.equalsIgnoreCase(Genre.ROMANCE.name())) {
            newGenre = Genre.ROMANCE;
        } else {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity("Not a valid novel type")
                    .type(MediaType.TEXT_PLAIN).build();
        }

        if (price < 5.0 || price > 50.0) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity("Price must be greater than 5 or lower than 50")
                    .type(MediaType.TEXT_PLAIN).build();
        }
        BooksPersistance
            .addBook(new Book(newGenre, newId, name, price, isbn, author));
        return Response.ok().build();
    }

    // ...

Similar to the @PathParam annotation in the function arguments, each @FormParam annotated argument will bind to the parameters the user entered in the form when submitting the POST request. In order to inform the user that a Book was successfully created, we can return a Response type. The response, among other things, can contain an entity which represents the actual response that the user will see, a typical HTTP status and the type of the entity you’re returning. In this implementation, we’re returning the HTTP code 400 BAD REQUEST if the user tries to create a book with an invalid genre or with a price lower than 5.0 or greater than 50.0. We return the HTTP 200 OK if the book was created with success.

And finally, the DELETE endpoint:

  // ...

    @DELETE
    @Path("/{id}")
    public Response deleteBook(@PathParam("id") int id) {
        Book book = BooksPersistance.findBookById(id);
        if (book != null) {
            BooksPersistance.deleteBook(id);
            return Response.ok().build();
        } else {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity("The book with id " + id + " does not exist")
                    .type(MediaType.TEXT_PLAIN).build();
        }

    }

  // ...

This should give you a nice overview on how to build RESTful endpoints with Jersey. The only thing left to do is to create some tests. Note that I will not create tests to ALL possible scenarios. This is only to give you a starting point to building your tests in your applications. Since the POST is the method with possible edge-cases, I’ll present you here some tests for it. I have some additional tests in my GitHub repository, but I haven’t written all the test cases in the code in this project since this is only for demonstration purposes and to serve as a basis for me if I forget how to do something basic with Jersey.

public class BookResourceTest extends JerseyTest {

  // ...

    /**
     * This tests if a book can be successfully created if valid parameters are
     * provided. We test if the response is of type 200 OK and if the
     * number of books increased by one.
     */
    @Test
    public void shouldCreateBook() {
        MultivaluedMap<String, String> params =
              new MultivaluedHashMap<String, String>();
        params.putSingle("name", "validName");
        params.putSingle("genre", "fiction");
        params.putSingle("price", new Double("20.5").toString());
        params.putSingle("isbn", "validisn");
        params.putSingle("author", "validAuthor");

        int nBooksBefore = BooksPersistance.numberOfBooks();

        Response response = target("book/new").request()
            .post(Entity.form(params));

        assertEquals(200, response.getStatus());

        assertEquals(nBooksBefore + 1, BooksPersistance.numberOfBooks());
    }

    /**
     * This tests the fact that a book should not be created if the form params
     * contain an invalid genre name. The response should be 400 BAD REQUEST
     * and the number of books should not be changed after emitting the request.
     */
    @Test
    public void shouldNotCreateBookNonExistantGenre() {
        MultivaluedMap<String, String> params =
            new MultivaluedHashMap<String, String>();
        params.putSingle("name", "validName");
        params.putSingle("genre", "notValidGenre");
        params.putSingle("price", new Double("20.0").toString());
        params.putSingle("isbn", "validisn");
        params.putSingle("author", "validAuthor");

        int nBooksBefore = BooksPersistance.numberOfBooks();

        Response response = target("book/new").request()
              .post(Entity.form(params));

        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(),
              response.getStatus());

        assertEquals(nBooksBefore, BooksPersistance.numberOfBooks());
    }

    /**
     * This tests the fact that a book should not be created if the form params
     * contains an invalid book price (< 5.0).
     * The response should be 400 BAD REQUEST and the number of books should not
     * be changed after emitting the request.
     */
    @Test
    public void shouldNotCreateBookPriceLowerThan5() {
        MultivaluedMap<String, String> params =
              new MultivaluedHashMap<String, String>();
        params.putSingle("name", "validName");
        params.putSingle("genre", "fiction");
        params.putSingle("price", new Double("4.99").toString());
        params.putSingle("isbn", "validisn");
        params.putSingle("author", "validAuthor");

        int nBooksBefore = BooksPersistance.numberOfBooks();

        Response response = target("book/new").request()
              .post(Entity.form(params));

        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(),
              response.getStatus());

        assertEquals(nBooksBefore, BooksPersistance.numberOfBooks());
    }

    // ...

}

Finally, let’s run our test suite and make sure every test is passing:

...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
...
Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.733 sec

Results :

Tests run: 7, Failures: 0, Errors: 0, Skipped: 0

This post should serve you as guidance towards building your own RESTful applications with Jersey. In this post, I covered the basics on how to setup your configuration in Eclipse and Maven in order to be able to use Jersey, how to start the built-in Jersey’s HTTP server, how to create basic GET, POST and DELETE endpoints and how to test your application. This is the basics of the framework, and I hoped it helped you getting started with this framework!

Please do comment if you think something is wrong if this helped you or not, or just criticize my awful code!