The Promise of CompletableFuture? JAX-RS2 example!


CompletableFuture API introduced in Java 8 is different from previous Future in the way it is composable and usable as a promise. Let’s see how to integrate it with JAX-RS 2.

Promise

A promise is a kind of handler representing an operation result which is maybe not yet available:

// Promise doesn't exist in the JVM, that's for the example
Promise promise = new Promise();
assertFalse(promise.isDone());
promise.setResult("end");
assertTrue(promise.isDone());

The main idea is to start some processing in a thread and continue without the result in the current thread. The promise will allow to use the result once there. That’s why it is often a pipeline of processing:

// Promise doesn't exist in the JVM, that's for the example
Promise promise = new Promise();
promise.then(result -> handleResult(result))  // will be triggered in the thread where the promise result is set
       .then(result2 -> convert(result2));

CompletableFuture to the rescue of JAX-RS 2

JAX-RS 2 client API introduced an asynchronous API relying on callbacks but also Future. This is the perfect candidate to convert it to a CompletableFuture and enable an asynchronous processing:

public CompletableFuture<MyResult> send(final String message) {
  CompletableFuture<MyResult> cf = new CompletableFuture<>(); // <1>
  Future<?> future = ClientBuilder.newBuilder().build()
      .target("http://...")
      .request()
      .accept(MediaType.APPLICATION_JSON_TYPE)
      .async() // <2>
      .post(Entity.entity(message, MediaType.APPLICATION_FORM_URLENCODED), new InvocationCallback<MyResult>() { // <3>
        @Override
        public void completed(final MyResult result) {
            cf.complete(result);
        }

        @Override
        public void failed(final Throwable throwable) {
            cf.completeExceptionally(throwable);
        }
      });
  cf = cf.whenComplete((result, exception) -> {  // <4>
   if (CancellationException.class.isInstance(exception)) {
     future.cancel(true);
   }
  });
  return cf;
}
  • we create our handler we’ll initialize later
  • we ensure our call will be async. Note: depending the implementation you use (conduit for CXF) it can means submitting the call in an executor service or using NIO API)
  • we do our JAX-RS invocation with an InvocationCallback which will be used to initialize our CompletableFuture in both success and failure cases
  • we don’t forget to propagate the cancel the user can call on the CompletableFuture to the underlying future JAX-RS provides us to really cancel the task

This pattern is not yet common for Java developpers but javascript ones know it since a long time. The nice part is the usage:

myService.send("test")
  .thenAccept(this::mapToDto)
  .thenAccept(this::validate);

Compare it to the Future usage where you have to call get and block immediately:

Future<?> f = myService.send("test")
MyResult result = f.get(timeout, timeoutUnit); // blocking
// then continue the processing

Asynchronous but…

First CompletableFuture usage doesn’t have to be asynchronous (it is not bound to a thread). This post first sample with Promise was not starting/using any special thread. However the benefit of CompletableFuture is used with asynchronous processing. This means you will work accross several threads. This is perfect until you mix it with several frameworks using ThreadLocal or well defined thread bound contexts.

Typically several security frameworks will be broken and some CDI contexts (@RequestScoped is the one we think immediately about for instance).

So we shouldn’t use this promising programming style? Actually you should! But now you leave the synchronous world keep in mind your threading model and when needed use message passing pattern (ie create a request object for your flow and stack all you need when you start it):

myService.send(new MyRequest(getPrincipal(), getImportantRequestAttribute(), "test"))
  .thenAccept(this::mapToDto)
  .thenAccept(this::validate);

This iesa bit the default paradigm but allows to go really further. This is really obvious when you depend on several external API and will reduce your latency.

Is CompletableFuture good enough or should I go for a real reactive framework

Of course the answer is “it depends”. CompletableFuture is, in my opinion, good enough for simple flows and very simple compositions. This means if your application uses this pattern in a localized area this will probably be good enough but if you use it widely – you can even wrap the EntityManager calls if your database is not fast enough – then you will likely need a more high level API. RxJava or reactor are then very good candidates to replace the JVM proposal. Note however this is an API you embrace or not. It means if you don’t use it everywhere – the whole pipeline – you will likely need in some places to block which can be fine or just break your original goal so keep an eye on the leaf usages of these API.

1 thought on “The Promise of CompletableFuture? JAX-RS2 example!

  1. miholc

    Another very useful, insightful and welcome article. CFs need these kinds of contributions, clarifying possible applications of this still-new-feeling feature.

    Reply

Leave a comment