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.

  • Request method annotations:
    • Jersey: uppercase: @GET, @POST, @PUT, @DELETE
    • Restlet: lowercase: @Get, @Post, @Put, @Delete

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>

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>

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

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

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