Java8 CompletableFuture API for Asynchronous Programming in springboot microservices

Introduction

Lambda expressions are by far the most discussed and emphasized feature of Java 8. While I agree that Lambdas being the salient feature in Java8, still there are lot more new things hidden behind the shadows of lambda expressions. Today we gonna take a look at one of them, the CompletionStage API [1] for leveraging asynchronous programming.

Java5 introduced the ExecutorService pattern where programmers can submit tasks to a pool of Threads. This yields a Future object and the only way to get the result from a future object is to call its get() method in the thread that submitted the task. This method is blocking, so this call will block the thread until the result is available to process. So, the use of  Future is ruled out on performance grounds. This is exactly where the CompletionStage comes to the rescue. [2]

What Is a CompletionStage

In a nutshell, a CompletionStage is a model that carries a task. A task can be an instance of Runnable, Consumer, or Function. The task is an element of a chain. CompletionStage elements are linked together in different ways along the chain. An "upstream" element is a CompletionStage that is executed before the element we are considering. Consequently, a "downstream" element is a CompletionStage that is executed after the element we are considering.

The execution of a CompletionStage is triggered upon the completion of one or more upstream  CompletionStages. Those CompletionStages might return values, and these values can be fed to this CompletionStage. The completion of this CompletionStage can also produce a result and trigger other downstream CompletionStages.

So a CompletionStage is an element of a chain.

The CompletionStage interface has an implementation called CompletableFuture. Note that CompletableFuture is also an implementation of the Future interface. CompletionStage does not extend Future. [2]

A task has a state:
  • It might be running.
  • It might be completed normally and might have produced a result.
  • It might be completed exceptionally and might have produced an exception.

Workshop

Let’s get into our usecase. In this section we are going to focus on how to use this asynchronous programming feature in a springboot microservice. Consider a client who needs to get the details of all the users in our system. Let’s assume we have two endpoints, one that returns set of user IDs of users in our system and the other returning the user details including the username and blog url given the aforementioned user IDs. We should expose one REST endpoint by orchestrating these two endpoints and that should return user details of all the users in our system in one go when a GET request is submitted against the url http://localhost:8080/users

That said, let’s take a look at our code. Let’s start with the service class UserDetailService first. The method we need to pay our attention is findUserDetails. I have two methods to fetch the data from two different downstream REST endpoints mentioned above. These two methods are straight forward and do not require any explanation. Notice how we start the CompletableFuture chain by invoking supplyAsync method with a supplier and then chaining it using thenApply by merely passing a function. Notice how composable and readable this CompletableFuture chain is.


Then let’s take a look at the controller class. There’s nothing much to explain here. The only important thing which is worth noting is that you can directly return a CompletableFuture from your REST controller/layer where T being the formal type parameter.


For the sake of completeness, I have added all the DTO classes which I used to serialize and deserialize between Java objects and the Json payloads via jackson databind.




So here’s the REST endpoint we exposed, http://localhost:8080/users. Upon the submission of a simple GET request like this,

curl http://localhost:8080/users

You should get the following response from the REST endpoint. 




Thread Model

It’s worth taking a look at the threading model used at a glance before we wind up. Notice that in the findUserDetails method of the service class I have not passed any executor service explicitly to the CompletableFuture chain. So if nothing is passed, by default it uses the common fork-join pool provided by the JDK. In my case the thread that executes the asynchronous tasks is ForkJoinPool.commonPool-worker-1. The name makes it obvious that it is obtained from the common fork-join pool.

But you still have the full control over it and have the liberty of hooking up your own thread pools easily. The CompletionStage API gives nice overloaded methods to serve this purpose. However I am not going to discuss these things in detail, since that is not the intention of this introductory article.



Conclusion

The CompletionStage interface and CompletableFuture class bring new ways of processing data asynchronously. This API is quite complex, mainly due to the number of methods exposed by the new interface and the new class, but that makes this API very rich with lots of opportunities for finely tuning asynchronous data processing pipelines to perfectly suit the needs of your applications.

This API is built on lambda expressions, leading to very clean and very elegant patterns. It gives a fine control for which thread should execute each task. It also allows the chaining and composition of tasks in a very rich way, and it has a very clean way of handling exceptions. [2]



References

[1] https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html
[2] https://community.oracle.com/docs/DOC-995305


Comments

Popular posts from this blog

Introducing Java Reactive Extentions in to a SpringBoot Micro Service

Optimal binary search trees

Edit distance