Wojciech Galanciak
Senior Eclipse Developer on the MyEclipse and Webclipse products.
Senior Eclipse Developer on the MyEclipse and Webclipse products.
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.
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:
Jersey is natively supported only by Glassfish.
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:
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:
There are 3 different classes generated automatically:
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>
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.
Let’s start with a feature which is directly supported by only one of the frameworks. RESTEasy provides the following caching features:
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.
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.
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):
There are three ways to configure it:
Note: It is possible to use TestNG instead of JUnit in Jersey Test Framework.
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:
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.
@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.
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!
If you have any comments or questions, we would love to hear from you @MyEclipseIDE on twitter or via the MyEclipse forum.