Java CompletableFuture API: Where Does it Stand in the Asynchronous Java Landscape?
While the Java CompletableFuture API was an important step for Java support for asynchronous programming, it's only part of the bigger picture.
In this article, we look at the Java CompletableFuture API, what it does, examples of how it works, and how it compares to other asynchronous programming practices in Java today.
What Is the Java CompletableFuture API?
The Java CompletableFuture API is a standard Java API for asynchronous programming.
Introduced in Java 8 back in 2014, the CompletableFuture API was a major step forward for supporting custom callbacks that get executed when the asynchronous operation completes. Unlike its predecessor, the Future.get(), the Java CompletableFuture API was not a blocking call.
The Reactive Manifesto
About the same time, the Reactive Manifesto was published (v1.0 in 2013, v2.0 in 2014). It takes asynchronous programming one step further and lays out the guidelines for building fully reactive systems. Reactive Manifesto suggests that a reactive system should be responsive, resilient, elastic, and message driven.
Inspired by the manifesto, the Reactive Streams project defines the interfaces for reactive streams in Java (1.0 released in 2015). Later on, Reactive Streams got standardized and included into the JDK as the Flow API as of Java 9 (2017). Notice that both Reactive Streams and Flow API provide just the interfaces — it is up to reactive frameworks like RxJava or Project Reactor to provide an actual implementation.
These two concepts make up most of the asynchronous programming landscape in Java today.
Java CompletableFuture API vs Reactive Streams
How do these two approaches — the Java CompletableFuture API and reactive streams — relate to each other? The main difference is that a CompletableFuture represents one result of an asynchronous call, while Reactive Streams is a pattern for pushing N messages asynchronously through a system. You can say that reactive streams take the same concept and generalize it one step further.
Therefore, CompletableFuture doesn’t (and couldn’t) address the elasticity requirement of the Reactive Manifesto by providing backpressure, as it operates on the level of a single call. For any reactive streams implementation, on the other hand, backpressure is a central concept.
What Problems Does Asynchronous Programming Solve?
Let’s take a step back and have a look at the overall benefits of asynchronous programming. Then return to specific technologies and see when you should choose which one.
Asynchronous Remote Server Calls
Unquestionably, the industry is currently moving from monolithic architectures to microservices. When your code makes a call to a remote service, you will not know for sure how quickly the other party will respond. Due to network latency, it will definitely be at least somewhat slower than doing the same work in the local JVM.
It does not make sense to block the server thread until you get the response — threads are an expensive resource. Instead, you can do some other processing for current request while you wait, or serve a different incoming request. Doing the remote service calls asynchronously lets you do just that, helping to get more out of your server resources.
Server Virtualization and Cloud Deployment
Another reality of modern software engineering, usually coming hand-in-hand with microservices, is server virtualization and deployment in the cloud. This builds the case for asynchronous programming even further: when you pay to a 3rd party Infrastructure-as-a-Service provider based on usage, and the cost of spawning an extra instance of each microservice piles up over time, asynchronous programming’s promise to use server resource more effectively starts to look very attractive.
There are other reasons for using asynchronous programming, including better user experience and cleaner code. If the problem at hand is parallel by nature, it is safer and more convenient to use the asynchronous programming tools available, rather than to mess with the Java threads manually.
When to Use Java CompletableFutures vs Reactive Streams?
Scenario 1: Single Calls to Microservices
The big plus for the Java CompletableFuture API is that it’s (relatively) simple and available out-of-the-box (part of the JDK).
When your code calls a remote microservice or a slow database to retrieve a specific result to fulfill the current request, it is probably NOT the place to use a reactive stream. One result is all you ever asked for. You are much better off using the methods in the CompletableFuture API to add a custom callback, specifying what to do with the result once it’s ready. In other words, this is a perfect use case for the CompletableFuture API.
Example scenario could be when you need to fetch invoices from 2 different remote systems, then combine the results and take some action on the outcome (e.g. show in the UI).
Scenario 2: A Stream of Asynchronously Processed Data
On the other hand, imagine a scenario where you are truly operating on a stream of messages. Let’s say you have an SMSGateway that receives SMS messages as they come in from users. Each message needs to be processed by SMSProcessor (a remote microservice) and contains a command that takes variable time to execute.
You could implement this scenario based on CompletableFuture API as well, but then you’d be reinventing things like backpressure: what if SMSProcessor is overwhelmed by previous messages, and not yet ready to receive more data? Such streaming scenarios are much better taken care of by the Reactive Streams approach, implemented by frameworks like RxJava or Project Reactor.
In short, there are use cases for both approaches: single asynchronous operations where CompletableFuture helps to keep things simple, and true streaming scenarios where you’ll want to use reactive streams.
Want to learn more about big changes to Java? Be sure to visit our new resource, Exploring New Features in Java. It has a ton of great resources for you to explore.
XRebel Provides Transparency in the Maze of Asynchronous Execution
One of the things you are unfortunately sacrificing when using asynchronous programming is the transparency of your code. As part of the code gets executed asynchronously, potentially in a separate thread, it gets harder to intuitively grasp what is actually going on.
This is where you could benefit from XRebel — a tool designed for monitoring Java web application performance and providing increased insight and visibility to the developer. As of XRebel 2020.1.0, XRebel provides explicit support for the Java CompletableFuture API, including a Gantt chart visualizing CompletableFuture tasks and their interdependencies.
Try it free for 10 days with an XRebel trial.