JAX-RS 2.1 - the latest & greatest

Version 2.1 (JSR 370) is the current release for the JAX-RS specification (at the time of writing). It is a part of the Java EE 8 Platform as well. This chapter will give you an overview (along with code examples) of all the new features in this release.

Here is what will be covered

Big ticket features in JAX-RS 2.1

  • Support for Server Sent Events (server and client)

  • JSON Binding (JSON-B) API integration

  • New Reactive client API

Other key enhancements

  • CompletionStage support in asynchronous server API

  • ExecutorService support in asynchronous Client API

  • JSON-P support enhancement

  • @Priority for custom providers

Support for Server Sent Events

Now JAX-RS has built-in support for the Server Sent Events standard (SSE) - represented by text/event-stream media type. JAX-RS defines a server as well as client side API (javax.ws.rs.sse package) - here is an overview

  • SseEvent represents a generic abstraction for a Server Sent Event

  • InboundSseEvent and OutboundSseEvent represent incoming and outgoing events respectively

  • Use OutboundSseEvent.Builder to create instance of an OutboundSseEvent

  • SseEventSink can be used to send individual OutboundSseEvents and a SseBroadcaster is used to manage multiple such SseEventSinks and create a simpler abstraction

  • A SseEventSource.Builder is used to create a SseEventSource which is a client side handle to process incoming SSE events (InboundSseEvents)

  • Last but not the least, we have Sse - the API construct which provides factory methods to create SseBroadcaster and OutboundSseEvent instances

Instances of Sse and SseEventSink can only be injected using @Context

Server API

On the server side, you can generate SSE events which clients (browser based or programmatic) can consume. Here is the high level flow to generate an event

  • Create an instance of an OutboundSseEvent

  • Send it using a SseEventSink

@GET //1
@Produces(MediaType.SERVER_SENT_EVENTS)
public void emit(@Context SseEventSink eventSink, @Context Sse util) { //2
    OutboundSseEvent sseEvent = util.newEvent("abhirockzz", new Date().toString()); //3
    eventSink.send(sseEvent); //4
    eventSink.close(); //5
}
  1. Client invokes the endpoint

  2. JAX-RS runtime injects instances of SseEventSink and Sse

  3. Create an instance of an OutboundSseEvent using the Sse#newEvent (factory method)

  4. Send it using a SseEventSink

  5. Close it - this ends the (long lived) connection b/w server and client

Another technique for instantiating an OutboundSseEvent is via Sse#newEventBuilder (another factory method) which allows you to set other events details in addition to name and data. These include - id, comment as well as a failure handling strategy using reconnectDelay method

There is more!

Sending a single event (or maybe a few) and saying goodbye to the client is fine, but its not what SSE is used for in general. It's used to transmit (real time) information like stock prices, game scores etc. The client is not required to ping/ask/inquire/poll the server repeatedly - instead, the server sends data (whenever available) on the (SSE) channel which is already open. This sounds like broadcast, doesn't it ? Its not a surprise that the JAX-RS API models it using SseBroadcaster

  • it follows the register (subscribe) and broadcast (publish) paradigm

  • is used to make it easier to handle multiple SSE clients

//registration process
...
private SseBroadcaster channel; //get handle to SseBroadcaster

@Path("subscribe")
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
public void subscribe(@Context SseEventSink eventSink, @Context Sse util){
    eventSink.send(util.newEvent("Subscription accepted. ID - "+ UUID.randomUUID().toString()));
    channel.register(eventSink);
}
//broadcasting to registered clients
...
private SseBroadcaster channel;

public void update(){
    OutboundSseEvent sseEvent = null;
    channel.broadcast(sseEvent);
}

Handling Custom types/objects

Information sent using the JAX-RS 2.1 SSE support does not only have to be of type java.lang.String – it supports Java primitives (Integer , Long etc.), JSON-B & JAX-B annotated types as well as custom objects whose encoding process (Java object to on-wire format) is defined using a MessageBodyWriter implementation

@GET
@Produces("text/event-stream")
public void fetch(@Context Sse sse, @Context SseEventSink eSink) {

        OutboundSseEvent stringEvent = sse.newEventBuilder()
                .name("stringEvent")
                .data(new Date().toString()).build();
        eSink.send(stringEvent);

        OutboundSseEvent primitiveTypeEvent = sse.newEventBuilder()
                .name("primitiveTypeEvent")
                .data(System.currentTimeMillis()).build();
        eSink.send(primitiveTypeEvent);

        OutboundSseEvent jsonbType = sse.newEventBuilder()
                .name("jsonbType")
                .data(new Employee("test@test", "test", 42))
                .mediaType(MediaType.APPLICATION_JSON_TYPE)
                .build();
        eSink.send(jsonbType);

        OutboundSseEvent jaxbType = sse.newEventBuilder()
                .name("jaxbType")
                .data(new Customer("testcut@test", "king"))
                .mediaType(MediaType.APPLICATION_XML_TYPE)
                .build();
        eSink.send(jaxbType);

        OutboundSseEvent customObjWithMBW = sse.newEventBuilder()
                .name("customObjectWithMessageBodyWriter")
                .data(new Student("stud@test", "stud-007")).build();
        eSink.send(customObjWithMBW);

        System.out.println("events sent");
        eSink.close();
        System.out.println("sink closed");
}

In this example

  • Multiple ​​OutboundSseEvents are created – each differing in the data/media type (text, json, xml etc.)

  • the default SSE media type is TEXT_PLAIN, hence does not need to be explicitly specified when dealing with String data type

  • ​​​​​Employee class is a JSON-B annotated class

  • Customer is a JAX-B annotated class

  • Student has a custom MesaageBodyWriter implementation

Here is the MesaageBodyWriter implementation for Student class

@Provider
@Produces(MediaType.TEXT_PLAIN)
public class StudentEncoder implements MessageBodyWriter<Student> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return type.isAssignableFrom(Student.class);
    }

    @Override
    public long getSize(Student t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Student t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        String info = t.getName()+ "," + t.getEmail();
        entityStream.write(info.getBytes());
    }

}

... and here is the Student POJO

//getters & setter omitted
public class Student {

    private String email;
    private String name;

    public Student() {
    }

    public Student(String email, String name) {
        this.email = email;
        this.name = name;
    }
...

Accessing the REST endpoint (e.g. http://localhost:8080/) will produce an output similar to the following i.e. you will get a SSE event with heterogeneous data types

event: stringEvent
data: Thu Aug 17 02:30:01 GMT 2017

event: primitiveTypeEvent
data: 1502937001704

event: jsonbType
data: {"name":"test","salary":42,"emp_email":"test@test"}

event: jaxbType
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><customer><email>testcut@test</email><name>king</name></customer>

event: customObjectWithMessageBodyWriter
data: stud-007,stud@test

Client API

You can use the JAX-RS SSE Client API to programatically access other SSE endpoints. The high level flow is as follows

  • build an instance of SseEventSource

  • define callbacks to handle incoming InboundSseEvents

  • open the channel and start accepting SSE streams

Let the code be our guide...

....
WebTarget target = ClientBuilder.newClient().target("https://sse.now.sh"); //1
SseEventSource eventSource = SseEventSource.target(target).build(); //2

eventSource.register(new Consumer<InboundSseEvent>() { 
                @Override //3
                public void accept(InboundSseEvent sseEvent) {
                    System.out.println("Events received in thread " + Thread.currentThread().getName());
                    System.out.println("SSE event recieved ----- " + sseEvent.readData());
                }
            },
                    new Consumer<Throwable>() { //4
                @Override
                public void accept(Throwable t) {
                    t.printStackTrace();
                }
            }, new Runnable() { //5
                @Override
                public void run() {
                    System.out.println("process complete");
                }
            });

eventSource.open(); //6
Thread.sleep(10000);
eventSource.close(); //7
  1. create the WebTarget instance (regular JAX-RS client code)

  2. build SseEventSource on top of the WebTarget instance

    Register callbacks

  3. Callback for a SSE event represented by InboundSseEvent instance

  4. Callback for handling error thrown at runtime

  5. Callback for logic to be executed after the events have been received

  6. call open - ready to accept events

  7. continue listening to events for sometime (10 secs in this case) and then close to terminate the SSE channel

Note on Thread safety - Sse, SseBroadcaster, SseEventSink, SseEventSource are thread safe

JSON Binding (JSON-B) integration

To complement its JAXB support, JAX-RS now includes first class support for JSON-B as well i.e. you can decorate your classes with JSON-B annotations and let the JAX-RS runtime deal with the JSON serialization and de-serialization. This is based on the already established Entity Provider based feature which supports a variety of Java types (e.g. primitive types, Reader, File etc.) including JSON-P objects (e.g. JsonValue)

A detailed disussion on JSON-B is out of scope of this book/chapter, but here is a high level overview. I would encourage you to dig into the JSON-B specification for more details

JSON-B quickie

It's a standard specification which defines a binding (serialization and deserialization) API between Java objects and JSON documents (RFC 7159 compatible)

  • Default mapping

JSON-B spec defines default mapping of Java classes and instances to equivalent JSON document components. It covers Java primitives (String, Boolean, Long etc.) and other types such as BigInteger, URL, Date etc.

For a simple POJO (Employee), the JSON-B API can be used as follows (in default mapping mode)

Employee empObj = new Employee("abhirockzz@gmail.com", "Abhishek");
Jsonb jsonb = JsonbBuilder.create();
String empJSON = jsonb.toJson(empObj);
empObj = jsonb.fromJson(empJSON);
  • Customized mapping: A bunch of annotations are defined in order to further customize the binding process (example coming up)

  • Reference Implementation: Yasson is the reference implementation for the JSON-B specification

Here is an example to highlight some of the JSON-B annotations

@Entity //JPA specific
@Table(name = "Employees") //JPA specific
@JsonbPropertyOrder(PropertyOrderStrategy.REVERSE) //1
public class Employee {

    @JsonbProperty("emp_email") //2
    @Id
    private String email;
    private String name;

    @JsonbTransient //3
    private int salary;

    public Employee() {
    }

    public Employee(String email, String name, int salary) {
        this.email = email;
        this.name = name;
        this.salary = salary;
    }

    //getters & setters ommitted for brevity
}
  1. @JsonbPropertyOrder specifies the lexicographical reverse (Z to A) order for JSON attributes i.e. Employee JSON form will have name followed by email

  2. @JsonbProperty is used to modify the name of the JSON attribute i.e. its not the same as the POJO field/variable name. In this case, the JSON representation for Employee will have the emp_email attribute instead of email

  3. @JsonbTransient tells the JSON-B runtime to ignore (not process) the specific property/field - we ensure that the Employee salary remains a secret!

How will these annotations be used at runtime within a JAX-RS application ? Another example to illustrate this

@Stateless
@Path("employees")
public class EmployeesResource {

    @PersistenceContext
    EntityManager em;

    @GET
    @Path("{email}")
    public Response test(@PathParam("email") String email) {

        TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e WHERE e.email = :email", Employee.class);
        query.setParameter("email", email);
        Stream<Employee> emps = query.getResultStream(); // new in JPA 2.2 !
        Employee emp = null;
        try {
             emp = emps.filter((e) -> e.getEmail().equals(email)).findFirst().get(); //1
        } catch (NoSuchElementException e) {
             return Response.status(404).entity("Employee '"+ email + "' not found").build();
        }
        return Response.ok(emp).build(); //2
    }

}
  1. Employee is searched - using email as the criteria

  2. Once found, the POJO representation is returned by the method. Thanks to the JSON-B integration, a JSON representation of Employee is returned to the caller

Note: in a situation where an entity can be treated as both JSON-B and JSON-P, the entity providers for JSON-B will take precedence over those for JSON-P unless the object is of JsonValue and its sub-types

Reactive Client API

The client API now supports the reactive paradigm using RxInvoker. Up until now, asynchronous operations were initiated with the help of AsyncInvoker - here is an example

Future<String> details = ClientBuilder.newClient().target("https://api.github.com/users").path("abhirockzz).request().async().get(String.class);

Chapter Asynchronous JAX-RS has more details

This is great, but its hard to chain API calls in a pipeline fashion e.g. do this, and when it finishes, do that - all asynchronously. JAX-RS does provide the InvocationCallback to handle this - but it's not a great fit for complex logic as it leads to a callback hell problem

JAX-RS 2.1 ships with CompletionStageRxInvoker (default implementation of RxInvoker) which is based on the (JDK 8) CompletionStage API

Client client = ClientBuilder.newClient();
CompletableFuture<User> userSearch = client.target("https://allusers.foo/").queryParam("email","abhirockzz@gmail.com").request().rx().get(User.class); //1
Function<String, CompletionStage<User>> userProfileSearch = new Function<>() { 
            @Override
            public CompletionStage<GithubProfile> apply(User user) {
                return client.target("https://api.github.com/users").path(user.getID()).request().rx().get(GithubProfile.class); //2
            }
        }

CompletableFuture<GithubProfile> result = userSearch.thenCompose(userProfileSearch); //3
String company = null;
try {
 GithubProfile profile = result.get(); //4
 company = profile.getCompany();
}
catch (Exception ex) {//handle...} 

return company;

Here is what's going on in the above example

  1. created a task (CompletableFuture) for searching a user by email

  2. created another task to find Github user profile

  3. chained the tasks to compose an asynchronous pipeline

  4. extract the result - invocation of result.get() triggers userSearch, followed by the profile search task

Open for extension

Its possible to plugin alternate implementations of RxInvoker as well

  • register the new Provider on the Client

  • declare the invoker in rx method call

Client client = ClientBuilder.newClient().register(MyRxProvider.class);
client.target("https://api.github.com/users").path("abhirockzz").request().rx(MyRxInvokerImpl.class).get(String.class);

Others

Here are some of the other smaller but important enhancements to the API

CompletionStage support in asynchronous server API

JAX-RS server side component now has support for returning a CompletionStage to mark the request as eligible for asynchronous processing - this is an addition to the AsyncResponse API which has been available since JAX-RS 2.0 (Java EE 7). The advantage this approach has over the AsyncResponse based API is that it is richer and allows you to create asynchronous pipelines

Let's go over an example to see this in action

@Path("cabs")
public class CabBookingResource {

    @Resource
    ManagedExecutorService mes;

    @GET
    @Path("{id}")
    public CompletionStage<String> getCab(@PathParam("id") final String name) {
        System.out.println("HTTP request handled by thread " + Thread.currentThread().getName());

        final CompletableFuture<Boolean> validateUserTask = new CompletableFuture<>();

        CompletableFuture<String> searchDriverTask = validateUserTask.thenComposeAsync(
                new Function<Boolean, CompletionStage<String>>() {
            @Override
            public CompletionStage<String> apply(Boolean t) {

                System.out.println("User validated ? " + t);
                return CompletableFuture.supplyAsync(() -> searchDriver(), mes);
            }
        }, mes);
        final CompletableFuture<String> notifyUserTask = searchDriverTask.thenApplyAsync(
                (driver) -> notifyUser(driver), mes);

        mes.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    validateUserTask.complete(validateUser(name));
                } catch (Exception ex) {
                    Logger.getLogger(CabBookingResource.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });

        return notifyUserTask;
    }

    boolean validateUser(String id) {
        System.out.println("searchDriverTask handled by thread " + Thread.currentThread().getName());
        System.out.println("validating user " + id);
        try {
            Thread.sleep(1500);
        } catch (InterruptedException ex) {
            Logger.getLogger(CabBookingResource.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }

    String searchDriver() {
        System.out.println("searchDriverTask handled by thread " + Thread.currentThread().getName());

        try {
            Thread.sleep(2500);
        } catch (InterruptedException ex) {
            Logger.getLogger(CabBookingResource.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "johndoe";
    }

    String notifyUser(String info) {
        System.out.println("searchDriverTask handled by thread " + Thread.currentThread().getName());

        return "Your driver is " + info + " and the OTP is " + (new Random().nextInt(999) + 1000);
    }

}
  • It starts with a HTTP GET to /booking/cabs/<user> which invokes the getCab method

    • the method returns a CompletionStage and returns immediately

    • the thread which served the request is now freed up

  • and then its about creating the asynchronous pipeline

    • we orchestrate the tasks for user validation and driver search using thenComposeAsync – this gives a CompletableFuture i.e. the searchDriverTask

    • we then supply a Function which takes the driver (returned by the above step) and invokes the notifyUser method – this is the CompletionStagewhich we actually return i.e. notifyUserTask – this is obviously executed later on, all we did was compose the sequence

  • once the process is completed (delays are introduced using Thread.sleep() ), the response is sent back to the user – internally, our CompletableFuture finishes

ExecutorService support in asynchronous Client API

The asynchronous execution support in Client API via the traditional Future based option (Invocation.Builder#async) or the new reactive client (Invocation.Builder#rx) is enhanced by the ability to define an ExecutorService which will be used to process these asynchronous tasks. This is possible using the executorService and scheduledExecutorService in ClientBuilder - you end up with a Client instance whose requests will be executed in the thread pool defined by the executor service

ExecutorService pool = Executors.newFixedThreadPool(10);
Client client = ClientBuilder.newBuilder().executorService(pool).build();
Future<String> result = client.target("http://foobar.com").path("jdoe").request().async().get(String.class);

The above is example for a standalone environment where a fixed pool of 10 threads will take care of the submitted tasks (Runnable, Callable). If you are in a Java EE environment (Java EE 7 and above), the container managed executor service (Java EE Concurrency Utilities) should be used

@Resource
ManagedScheduledExecutorService managedPool; //container in action

Client client = ClientBuilder.newBuilder().scheduledExecutorService(managedPool).build();
Future<String> result = client.target("http://foobar.com").path("jdoe").request().async().get(String.class);

Sub Resource locators

Up until now (JAX-RS 2.0), sub-resource locators could only return an object - now it's possible for them to return a class

@Path("conferences")
public class ConferencesResourceLocator{

    @Path("{id}") 
    public Class pick(@PathParam("id") String confID){
        if(confID.equals("devoxx")){
            return DevoxxResource.class;
        }else if(confID.equals("JavaOne")){
            return JavaOneResource.class;
        }
    }

}

In the above example, rather than returning instances of our resource classes, the explicit class is returned - the JAX-RS runtime takes care of creating an instance as per existing rules laid out by the specification

Sub-resource locators were discussed in chapter JAX-RS Core Part I

JSON-P support enhancement

JsonString and JsonNumber (derivatives of JsonValue) have been included as the supported sub-type i.e. JAX-RS now has entity providers for them as well

Provider @Priority

You can now decorate your custom provider imeplemtations with @Priority to help JAX-RS runtime choose the appropriate one at runtime - given you have multiple such providers. Points to note

  • javax.ws.rs.Priorities.USER is the default value

  • lower priority will be preferred e.g. @Priority(2) will be chosen over @Priority(5)

  • in a scenario where two or more providers have the same priority, then its upto the implementation to define which gets chosen

Last updated