Jersey vs Restlet - comparative examples
Work in progress page!
Jersey is the JAX-RS reference implementation, Restlet is a popular API for creating RESTful web services. Restlet provides also a JAX-RS extension but by default its syntax is quite different from that standard. Examples provided in this page could be useful if you are planning to port some code from one library to the other.
General differences
- Request method annotations:
- Jersey: uppercase:
@GET
,@POST
,@PUT
,@DELETE
- Restlet: lowercase:
@Get
,@Post
,@Put
,@Delete
Maven
Jersey is available on Maven Central, while for Restlet you need to add following section in your pom.xml:
<repositories> <repository> <id>maven-restlet</id> <name>Public online Restlet repository</name> <url>http://maven.restlet.com</url> </repository> </repositories>
Endpoint configuration
Following example defines a service having an endpoint on /myservice/resource/info
.
Jersey
ApplicationConfig
, generated by the IDE (Netbeans), defines the service base path:@javax.ws.rs.ApplicationPath("myservice") public class ApplicationConfig extends javax.ws.rs.core.Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> resources = new java.util.HashSet<>(); addRestResourceClasses(resources); return resources; } /** * Do not modify addRestResourceClasses() method. * It is automatically populated with * all resources defined in the project. * If required, comment out calling this method in getClasses(). */ private void addRestResourceClasses(Set<Class<?>> resources) { resources.add(net.zonia3000.jaxrsdemo.MyResource.class); } }
- Resource classes define the other parts of the path using the
@Path
annotation (it can be used both on the resource class and on each method handling requests):@Path("resource") public class MyResource { @GET @Path("info") public String getInfo() { return "your information"; } }
Restlet
- All endpoints are defined into the Application class:
public class MyApplication extends org.restlet.Application { /** * Creates a root Restlet that will receive all incoming calls. */ @Override public synchronized Restlet createInboundRoot() { Router router = new Router(getContext()); router.attach("/myservice/resource/info", MyResource.class)); return router; } }
- Resource classes define listen methods using annotations:
public class MyResource extends org.restlet.resource.ServerResource { @Get public Representation getInfo() { return new StringRepresentation("your information"); } }
- web.xml configuration:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <display-name>MyApplication</display-name> <!-- Restlet adapter --> <servlet> <servlet-name>RestletServlet</servlet-name> <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class> <init-param> <!-- Application class name --> <param-name>org.restlet.application</param-name> <param-value>net.zonia3000.restlet.MyApplication</param-value> </init-param> </servlet> <!-- Catch all requests --> <servlet-mapping> <servlet-name>RestletServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Filtering
Jersey
- You need to create an annotation for you filter:
@NameBinding @Retention(RetentionPolicy.RUNTIME) public @interface OnlyAuthorized { }
- Filter implementation:
@Provider @OnlyAuthorized public class AuthorizationRequestFilter implements javax.ws.rs.container.ContainerRequestFilter { @Inject HttpServletRequest request; @Override public void filter(ContainerRequestContext requestContext) throws IOException { boolean authorized = false; /* do your filtering operations here [...] */ if (!authorized) { requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build()); } } }
- Put the filter annotation on your resource class:
@OnlyAuthorized @Path("resource") public class MyResource { }
Restlet
- Implement the filter:
public class AuthorizationRequestFilter extends org.restlet.routing.Filter { public AuthorizationRequestFilter(Context context) { super(context); } @Override public int beforeHandle(Request request, Response response) { boolean authorized = false; /* do your filtering operations here [...] */ if (authorized) { return Filter.CONTINUE; } response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); return Filter.STOP; } }
- Add the filter in the Application configuration class:
public class MyApplication extends org.restlet.Application { @Override public synchronized Restlet createInboundRoot() { Router router = new Router(getContext()); Filter authFilter = new AuthorizationRequestFilter(getContext()); authFilter.setNext(MyResource.class); router.attach("/myservice/resource/info", authFilter)); return router; } }
JAXB
How to represent JAXB classes (classes annotated using @XmlRootElement
).
Jersey
You simply need to return a MyJAXBClass
in a request handler method annotated with the proper media type in @Produces
and Jersey will automatically marshal it in XML.
@Path("resource") public class MyResource { @GET @Produces(MediaType.APPLICATION_XML) public MyJAXBClass getXML() { MyJAXBClass jaxbOject = getMyJAXBObject(); return jaxbOject; }
Restlet
public class MyResource extends ServerResource { @Get public Representation getXML() { return new WriterRepresentation(MediaType.TEXT_XML) { @Override public void write(Writer writer) throws IOException { MyJAXBClass jaxbOject = getMyJAXBObject(); JAXB.marshal(jaxbOject, writer); } }; } }
Client - Big file upload
Jersey
You MUST set CHUNKED settings to avoid “OutOfMemoryError: Java heap space
” due to sun.net.www.http.PosterOutputStream
used by Jersey implementation.
import org.glassfish.jersey.client.ClientProperties; public class MyClient { private static final int BUFFER_SIZE = 1024; public Response uploadFile(final File file) throws GeneralSecurityException, IOException { WebTarget target = ClientBuilder.newClient() .property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024) .property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED") .target(baseURL) .path("myservice").path("file") .queryParam("fileName", file.getName()); StreamingOutput out = new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { byte[] chunk = new byte[BUFFER_SIZE]; try (FileInputStream is = new FileInputStream(file)) { int available; while ((available = is.available()) > 0) { if (available >= BUFFER_SIZE) { is.read(chunk); output.write(chunk); } else { output.write(is.read()); } } } } }; Response response = target.request().post(Entity.text(out)); return response; } }
Restlet
Restlet doesn't suffer from the OutOfMemoryError
big file upload issue affecting Jersey.
public Status uploadFile(final File file, MediaType mediaType) throws IOException { Form fileForm = new Form(); fileForm.add(Disposition.NAME_FILENAME, file.getName()); Disposition disposition = new Disposition(Disposition.TYPE_INLINE, fileForm); FileRepresentation entity = new FileRepresentation(file, mediaType); entity.setDisposition(disposition); FormDataSet fds = new FormDataSet(); fds.setMultipart(true); FormData fdFile = new FormData("fileToUpload", entity); fds.getEntries().add(fdFile); Reference reference = new Reference(userSpaceBaseURL) .addSegment("myservice").addSegment("file"); ClientResource cr = getClientResource(reference); cr.post(fds); return cr.getStatus(); }