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 integrationNew
Reactiveclient API
Other key enhancements
CompletionStagesupport in asynchronous server APIExecutorServicesupport in asynchronous Client APIJSON-Psupport enhancement@Priorityfor 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
SseEventrepresents a generic abstraction for a Server Sent EventInboundSseEventandOutboundSseEventrepresent incoming and outgoing events respectivelyUse
OutboundSseEvent.Builderto create instance of anOutboundSseEventSseEventSinkcan be used to send individualOutboundSseEvents and aSseBroadcasteris used to manage multiple suchSseEventSinks and create a simpler abstractionA
SseEventSource.Builderis used to create aSseEventSourcewhich 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 createSseBroadcasterandOutboundSseEventinstances
Instances of
SseandSseEventSinkcan 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
OutboundSseEventSend 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
}Client invokes the endpoint
JAX-RS runtime injects instances of
SseEventSinkandSseCreate an instance of an
OutboundSseEventusing theSse#newEvent(factory method)Send it using a
SseEventSinkClose it - this ends the (long lived) connection b/w server and client
Another technique for instantiating an
OutboundSseEventis viaSse#newEventBuilder(another factory method) which allows you to set other events details in addition tonameanddata. These include -id,commentas well as a failure handling strategy usingreconnectDelaymethod
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 withStringdata typeEmployeeclass is aJSON-Bannotated classCustomeris aJAX-Bannotated classStudenthas a customMesaageBodyWriterimplementation
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@testClient 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
SseEventSourcedefine callbacks to handle incoming
InboundSseEventsopenthe 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(); //7create the
WebTargetinstance (regular JAX-RS client code)build
SseEventSourceon top of theWebTargetinstanceRegister callbacks
Callback for a SSE event represented by
InboundSseEventinstanceCallback for handling error thrown at runtime
Callback for logic to be executed after the events have been received
call
open- ready to accept eventscontinue listening to events for sometime (10 secs in this case) and then
closeto terminate the SSE channel
Note on Thread safety -
Sse,SseBroadcaster,SseEventSink,SseEventSourceare 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
}@JsonbPropertyOrderspecifies the lexicographical reverse (Z to A) order for JSON attributes i.e.EmployeeJSON form will havenamefollowed byemail@JsonbPropertyis 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 forEmployeewill have theemp_emailattribute instead ofemail@JsonbTransienttells the JSON-B runtime to ignore (not process) the specific property/field - we ensure that theEmployeesalaryremains 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
}
}Employeeis searched - usingemailas the criteriaOnce found, the POJO representation is returned by the method. Thanks to the JSON-B integration, a JSON representation of
Employeeis 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
created a task (
CompletableFuture) for searching a user by emailcreated another task to find Github user profile
chained the tasks to
composean asynchronous pipelineextract the result - invocation of
result.get()triggersuserSearch, 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
rxmethod 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
CompletionStage support in asynchronous server APIJAX-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 thegetCabmethodthe method returns a
CompletionStageand returns immediatelythe 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 aCompletableFuturei.e. thesearchDriverTaskwe then supply a
Functionwhich takes the driver (returned by the above step) and invokes thenotifyUsermethod – this is theCompletionStagewhich 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, ourCompletableFuturefinishes
ExecutorService support in asynchronous Client API
ExecutorService support in asynchronous Client APIThe 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
JSON-P support enhancementJsonString 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
@PriorityYou 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.USERis the default valuelower 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
Was this helpful?