Introduction to CompletableFuture
CompletableFuture was one of the feature released in Java 8. CompletableFuture implements the CompletionStage and Future interfaces. It is mainly used for asynchronous programming in Java.
Asynchronous programming is a approach of writing non-blocking code by running a task on separate threads rather than main application’s thread and notifying the main thread about the progress status.
This approach will not lead main thread to be blocked for any task completion and it can execute multiple other tasks parallely.
This parallel working approach definitely improves the performance.
Hierarchy of CompletableFuture
Future vs CompletableFuture
CompletableFuture can be considered as an extension to Future API which was introduced in Java version 5.
Future provides two methods – isDone() and get() method. isDone() method checks whether the computation is completed or not and get()
method retrieves the result of the computation when it is done. Thus, A Future can be used as a reference to the result of an asynchronous computation.
Future API definitely lacks some important features :
Limitations of Future
Manual completion is not possible
Completion of Future manually is not possible. Let us understand this with example , suppose we have one function which fetches data from a remote API. Data here is the latest price of the product. As API calls are time consuming , we are running it in separate threads.
In case , the remote service is down and now you required to complete the Future manually with the cached data. It will not be possible with the Future.
Performing further actions on a Future’s result is not possible without blocking
One of the major limitation of Future is that it never notifies the completion of execution. Future API has a
get()
method and this method blocks the code until the result is not returned by Future.We can not attach any callback function with the Future to make it automatically called when the Future’s result is returned.
Chaining of multiple Futures is not possible
With future, we can not create asynchronous tasks to be performed.
For Example : If you want to perform one task and another task after completion of first task and so on. This is not possible with Future.
Multiple Futures cannot be combined together
Suppose, we need to perform n-number of different tasks parallely with n-number of different Futures. This is not possible with this API.
Lack of Exception Handling
One major limitation of Future API that it lacks the exception handling construct.
How to create CompletableFuture
In below examples, we will learn :
- How to create CompletableFuture
- Uses of
runAsync()
method - Uses of
supplyAsync()
method
1. Basic example to create CompletableFuture
The simple CompletableFuture can be created using its no-arg constructor :
CompletableFuture<String> completableFuture = new CompletableFuture<String>();
To get the result of this CompletableFuture , simple get() method needs to be called :
String output = completableFuture.get()
As we know, The get()
method blocks the code until the Future gets completed and return result. So, the above execution will block forever as the Future is never completed.
So we can use complete()
method of CompletableFuture to manually complete a Future –
completableFuture.complete("The result of Future")
All the threads which are waiting for this Future will get the result also all the subsequent calls to completableFuture.complete()
will get ignored.
2. Uses of runAsync()
to run asynchronous computation
CompletableFuture’s runAsync()
methods can be used if you want to run background tasks asynchronously and do not want any result returned from the task, This method takes a Runnable object as argument and returns CompletableFuture<Void>
.
// Running the task asynchronously.
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
// Below is the task
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
throw new IllegalStateException(e);
}
System.out.println("Separate thread");
}
});
// Block the code and wait for the completableFuture to complete
completableFuture.get()
To reduce the lines of code, we can also use lambda expression :
// Running the task asynchronously.
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync( () -> {
// Below is the task
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
throw new IllegalStateException(e);
}
System.out.println("Separate thread");
});
3. Uses of supplyAsync()
to run a task asynchronously and also return the result
runAsync()
method of CompletableFuture
works great when computations needs to be performed asynchronously but it does not return result. If you want asynchronous computation but also want result then supplyAsync()
method is used.
This method takes a Supplier<T> as argument and returns CompletableFuture<T>
.
T denotes the type of the value obtained by calling the given supplier –
// Running a task specified by a Supplier object asynchronously
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result";
}
});
// Block the code and get the result
String output = completableFuture.get();
System.out.println(output);
A Supplier<T> is a functional interface. It represents a supplier of results. It has one method get()
where you can write your asynchronous background task and return the result.
To reduce the lines of code, we can also use lambda expression :
// With Lambda Expression
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result";
});
Conclusion
That’s all folks! In this article, you have got the complete overview of CompletableFuture in Java.