facebook
Wojciech Galanciak
Senior Eclipse Developer on the MyEclipse and Webclipse products.
Posted on Nov 25th 2015

Introduction

Two frameworks have emerged to simplify the development of RESTful web services and applications in Java—Jersey and RESTEasy. These frameworks are two of the most popular implementations of the JAX-RS standard. Both frameworks provide a nice feature set that simplifies the development of REST APIs. Sometimes the implementation is different, but their capabilities are very similar.

The purpose of this article is to compare the differences between the two frameworks. The focus is on features unique to each framework as well as features where the approaches differ significantly. The main goal is to provide some additional context for those who are trying to choose between the two. This document was created for RESTEasy 3.0.13 and Jersey 2.22.1.

Tip:  Refer to the Jax-RS documentation if you are just starting your journey with REST APIs in Java and would like more information.

Project Creation

The first step to start work with any kind of framework is to set up a project configuration. RESTEasy makes it easier to get started because it comes bundled with the following servers:

  • JBoss AS 7
  • JBoss EAP 6.1
  • Wildfly

Jersey is natively supported only by Glassfish.

On a remote dev team? Try CodeTogether—it’s free!
  • Live share IDEs & coding sessions
  • See changes in real time
  • Cross-IDE support for VS Code, IntelliJ & Eclipse
  • Guests join from Browser or IDE
  • End-to-end source encryption
  • www.codetogether.com

Configuring Jersey

To have a better view, let’s assume our project is going to work in the Servlet 3.0 container environment. Jersey is built using Maven and also uses it extensively in project creation and configuration. There are two dedicated archetypes:

  • jersey-quickstart-grizzly2—for creating a standalone project that runs on top of a Grizzly container
  • jersey-quickstart-webapp—for creating web application skeletons

The grizzly2 archetype is a perfect way to start exploring Jersey capabilities. Execute the following command:

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.21

 

You get a new Jersey application out of the box:
JerseyApp

There are 3 different classes generated automatically:

  • MyResource—an example implementation of a resource
  • MyResourceTest—a JUnit test of this web service that utilizes a standalone Grizzly instance to provide an easy way to write JUnit tests for defined web services
  • Main—a class that uses the local Grizzly Container instance (by default on http://localhost:8080/myapp/) to expose services to test them with 3rd party tools

This project is perfect to learn about framework without struggling with server configuration and more complex project definition. Also, it does not require any knowledge about other Java web technologies.

The webapp archetype generates a web project with Jersey. Notice that it uses configuration through web.xml. We are focused on Servlet 3.0 so it is not required anymore.

Of course such example projects are useful only at the start. It is more common that RESTful services are added to an existing application or an application which implements a specific framework. In such cases configuration is also very simple. Jersey requires only one Maven dependency:

<dependency>
     <groupId>org.glassfish.jersey.containers</groupId>
     <artifactId>jersey-container-servlet</artifactId>
     <version>2.21</version>
</dependency>

After this simple modification and Maven dependency resolution you can start implementing your resources with Jersey.

During project building you may see the following error:

`[ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.2:war (default-war) on project simpleApplication: Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)`

This is because we use Servlet 3.0 support where web.xml is no longer required. To avoid build failure on the missing web.xml file you need to add the following plugin to your build configuration in pom.xml:

<plugin>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.6</version>
     <configuration>
          <warSourceDirectory>WebContent</warSourceDirectory>
          <failOnMissingWebXml>false</failOnMissingWebXml>
     </configuration>
</plugin>

Configuring RESTEasy

The situation is different in the case of RESTEasy. There are no official archetypes for getting started. In this case, configuration steps for an existing application and new project creation are the same. There is one main dependency required by RESTEasy:

<dependency>
     <groupId>org.jboss.resteasy</groupId>
     <artifactId>resteasy-jaxrs</artifactId>
     <version>3.0.9.Final</version>
</dependency>

As far as we consider our testing environment (Servlet 3.0 container) RESTEasy provides support for it via the ServletContainerInitializer integration interface. In this case one more dependency is required:

<dependency>
     <groupId>org.jboss.resteasy</groupId>
     <artifactId>resteasy-jaxrs</artifactId>
     <version>3.0.9.Final</version>
</dependency>

It is very important to add this dependency, otherwise our resources will not be accessible without any errors pointing to the possible cause.

Caching

Let’s start with a feature which is directly supported by only one of the frameworks. RESTEasy provides the following caching features:

  • Cache-Control header control
  • server-side in memory cache
  • client-side in memory cache

The first one is a set of two annotations—@Cache and @NoCache. They can be used only with @GET annotated methods. If such method has @Cache then for a successful response (with 200 OK response code) it automatically sets a Cache-Control header.

Here is an example usage of the @Cache annotation where we additionally require revalidation in the case of any POST/PUT/DELETE request on this resource:

@GET
@Path("users/{id}")
@Produces("application/json")
@Cache(mustRevalidate = true)
public Response getUser(@PathParam("id") String id) {}

The corresponding @NoCache annotation explicitly defines by `Cache-Control: nocache` header that we do not want anything to be cached.

The second caching feature is dedicated to RESTEasy Client API. It can be applied with both ClientRequest and Client Proxy Framework. In the case when response allows client to cache then it is cached in a local memory.

Here is a simple example of client-side caching with ClientRequest:

RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
LightweightBrowserCache cache = new LightweightBrowserCache();
ClientRequest request = new ClientRequest("http://localhost:8080/users/1");
CacheFactory.makeCacheable(request, cache);

The last feature works on a server-side and is responsible for caching responses for GET method invocations. To enable it in the application an instance of `org.jboss.resteasy.plugins.cache.server.ServerCacheFeature` needs to be registered via Application.getSingletons() or Application.getClasses(). The direct access to the cache can be obtained by injecting `org.jboss.resteasy.plugins.cache.ServerCache` via the @Context annotation. Under the hood you will find the Infinispan library which provides the data storage mechanism for this caching feature.

What do we have in Jersey in this domain? Actually there is only a less elegant way for setting response headers. The following example is an equivalent to the @Cache annotation in RESTEasy:

@GET
@Path("users/{id}")
@Produces("application/json")
public Response getUser(@PathParam("id") String id) {
.
.
.
	CacheControl cache = new CacheControl();
	cache.setMustRevalidate(true);
	return Response.ok(user).cacheControl(cache).build();
}

Of course it is also possible to write the same annotation for Jersey. Learn how.

GZIP Compression

Although nowadays network speed may be very high, there are still areas where it may be limited. One example, and the most important, is a mobile world. Fortunately, a response body is usually just a text so it can be easily compressed on a server side and then decompressed on a client device. Let’s assume that we want to support communication both ways (compressed requests and responses) and that we want to selectively compress only those long enough responses.

RESTEasy provides out of the box support for GZIP compression and decompression. It means that if our service receives a request with Content-Encoding equals to gzip then such message body is decompressed automatically. The same situation is with a response—Content-Encoding header set to gzip triggers response body compression.  To avoid manual header configuration RESTEasy provides an @GZIP annotation:

@GET
@Produces("application/json")
@GZIP
public String getUsers() {
     ...
}

The main downside of this approach is that gzip support can be defined only on methods level. It meets our requirement (applying selectively), but limits the flexibility.

Again in Jersey things need to be done more manually by using Interceptors API. Firstly, let’s fill a lack of @GZIP annotation with our own annotation:

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Gzip {}

This annotation allows us to bind a dedicated Gzip interceptor only to the specific methods:

@Provider
@Gzip
public class GZIPWriterInterceptor implements WriterInterceptor {
 
    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

This result interceptor is executed only for methods with an @Gzip annotation. In the same way, we may implement reader interceptor to decompress request body.

Testing

There are several ways to test your JAX-RS API. For example, you can perform real requests to a test server dedicated to the test environment. Or, you can use a lightweight standalone server on which livecycle is fully controlled directly from the test code. Another approach is to mock the server itself. Let’s see how testing is supported by our two frameworks.

RESTEasy does not provide a wide range of testing tools. The first one is a Mock Framework which applies the last approach mentioned above. Instead of sending a real request to the server, we are able to create mocks for both request and response and then pass them directly to a particular method. Here is an example how it works in practice:

Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();

POJOResourceFactory factory = new POJOResourceFactory(UserResource.class);
  dispatcher.getRegistry().addResourceFactory(factory);
// create mock request
MockHttpRequest request = MockHttpRequest.get("/users/1");
// create mock response
MockHttpResponse response = new MockHttpResponse();
// invoke mocked request
dispatcher.invoke(request, response);

// check if e.g. response code is correct
Assert.assertEquals(HttpServletResponse.SC_OK, response.getStatus());

There is also a set of plugins for RESTEasy which provides different embeddable servers. Unfortunately each has its own API and there is no unified way to use them in your tests.  This is where Jersey has a lot more to offer. It also has a set of extensions for different standalone containers, but more importantly, it provides something called Jersey Test Framework. How does it work? Here a simple example for UserResource:

public class UserResourceTest extends JerseyTest {
 
    @Override
    protected Application configure() {
        return new ResourceConfig(UserResource.class);
    }
 
    @Test
    public void test() {
        final Response response = target("users/1").request().get();
        // some assertions
    }
}

This test will actually start a standalone test container. There are several different containers which can be used (a similar set is also supported by RESTEasy):

  • Grizzly
  • Jetty
  • HTTP Server from JDK
  • Simple
  • In Memory (not a real container – no network communication)

There are three ways to configure it:

  • do nothing—Jersey picks up TestContainerFactory from a classpath,
  • override JerseyTest#getTestContainerFactory() to define TestContainerFactory explicitly on the compilation level
  • set a system variable TestProperties#CONTAINER_FACTORY to move its declaration to the execution level

Note: It is possible to use TestNG instead of JUnit in Jersey Test Framework.

MVC

For the Model-View-Controller design pattern we can observe two completely different approaches. On one side there is generic MVC support in Jersey. On the other we have Spring integration in RESTEasy.

In Jersey architecture as a model we can consider returned value by a resource method, a controller is a resource class and a view is a template that consumes the model. We are already familiar with two of them, but what about a view? There are two ways to bind a model to a view—explicit and implicit.

The explicit approach utilizes the Viewable class which can be returned by a method, for example:

@GET
@Path("users/{id}")
public Viewable getUser(@PathParam("id") String id) {
        .
	.
	return new Viewable(“user.details”, user);
}

In the code above a User play instance is associated with a particular template. The implicit approach uses the @Template annotation instead:

@GET
@Path("users/{id}")
@Template(“user.details”)
public User getUser(@PathParam("id") String id) {
        .
	.
	return user;
}

The annotated method behaves in the same way as in the first approach, it returns a Viewable instance. Also in both cases the media type of a response is determined by the @Produces annotation.

In the case of the template engine there are several options available through extension modules:

  • Mustache
  • Freemarker
  • JSP (with some limitations)

As mentioned earlier, MVC support in RESTEasy is provided by Spring MVC integration. It can be applied by using Spring DispatcherServlet. The most important outcome is that Spring ModelAndView objects can be used as a return argument from GET resources. What is a downside in a comparison to Jersey? Definitely complexity level, especially when a developer is not familiar with the Spring Framework but would like to use MVC architecture.

Async API – Chunked Output

Since JAX-RS 2.0, asynchronous HTTP support has become a part of the specification through the @Suspended annotation and AsyncResponse interface. Both Jersey and RESTEasy provide their own implementation. The difference is that Jersey additionally provides something called Chunked Output. It allows the server to send back to the client a response in parts (chunks). This approach is very useful for long processing responses for which parts of data are available before the whole response data is prepared. In result, chunks can be pushed one by one to the client during request processing.

@Path("/resource")
public class AsyncResource {
    @GET
    public ChunkedOutput<String> getChunkedResponse() {
        final ChunkedOutput<String> output = new ChunkedOutput<String>(String.class);
 
        new Thread() {
            public void run() {
                try {
                    String chunk;
 
                    while ((chunk = getNextString()) != null) {
                        output.write(chunk);
                    }
                } catch (IOException e) {
                    // IOException thrown when writing the
                    // chunks of response: should be handled
                } finally {
                    output.close();
                        // simplified: IOException thrown from
                        // this close() should be handled here...
                }
            }
        }.start();
 
        // the output will be probably returned even before
        // a first chunk is written by the new thread
        return output;
    }
 
    private String getNextString() {
        // ... long running operation that returns
        // 	next string or null if no other string is accessible
    }
}

As you can see above, resource method returns ChunkedOutput just after the data processing thread is started.

Summary

Although we are looking at implementations of the same specification, both of them provide different additional features. If you are starting to learn JAX-RS, then Jersey provides some out of the box Maven archetypes to bootstrap development. On the other hand, RESTEasy is available in the JBoss server family by default. RESTEasy’s additional features focus on automation (caching and gzip support) to relieve a user from implementing some common capabilities for each application separately. However, Jersey has better testing infrastructure. It’s hard to pick a winner – the best approach is to make a decision based on what features and capabilities are important for your specific needs. Good luck!

Let Us Hear from You!

If you have any comments or questions, we would love to hear from you @MyEclipseIDE on twitter or via the MyEclipse forum