“CompletableFuture” in Java is a useful feature that helps in writing asynchronous and non-blocking code. It is such that it runs a task on a separate thread rather than the main application thread and lets the main thread know about its status i.e., completion, failure, etc.
Moreover, it is of great aid in writing scalable, responsive, and efficient code with the help of multi-core processors and handling various complex asynchronous workflows.
Contents Overview
- What is CompletableFuture in Java?
- CompletableFuture Methods
- What are the Differences Between CompletableFuture and Future?
What is CompletableFuture in Java?
“CompletableFuture” is a class in the “java.util.concurrent” package that implements the Future and CompletionStage interface. It is such that it indicates a future output of an asynchronous calculation. It can be considered as a container containing the result of an asynchronous operation executed on a separate thread.
CompletableFuture Methods
The “ComparableFuture” class provides some methods to apply operations on the outcome of an async calculation, explained below:
Methods | Functionality |
---|---|
supplyAsync() | This method completes its task asynchronously. It retrieves CompletableFuture to which the other methods can be applied. |
join() | It retrieves the result value when complete and returns a CompletionException if an exception is faced. |
thenApply() | This method takes the function as an argument and retrieves a new CompletableStage upon the stage being completed. |
runAsync() | This method runs a task asynchronously without retrieving a value. |
thenComposeAsync() | It has the result of the first CompletableFuture object as input and gives another CompletableFuture object as output. |
thenAcceptAsync() | It consumes the result of a task asynchronously, without retrieving a value. |
thenApplyAsync() | It processes the result of a task asynchronously and retrieves a new CompletableFuture with the changed result. |
allOf() | It combines multiple futures in one future. |
Example 1: Creating a CompletableFuture in Java
This example creates a CompletableFuture with the help of the “supplyAsync()” method:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<String> message = CompletableFuture.supplyAsync(() -> {
return "This is JavaBeat!";
});
System.out.println(message.get());
}}
In this code, create a “CompletableFuture” that runs a lambda function passed to the “supplyAsync()” method in a separate thread. As soon as the execution completes, the resultant lambda function is returned via the CompletableFuture object.
Output
This output confirms that the resultant lambda function is returned with the contained message.
Example 2: Composing a CompletableFuture in Java
The following demonstration uses CompletableFuture to compose various asynchronous operations:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<String> message1 = CompletableFuture.supplyAsync(() -> "Java");
CompletableFuture<String> message2 = CompletableFuture.supplyAsync(() -> "Programming");
CompletableFuture<String> combineMessages = message1.thenCombine(
message2, (x1, x2) -> x1 + " " + x2);
System.out.println(combineMessages.get());
}}
The code explanation is as follows:
- Create two CompletableFuture instances of “String” type that get “Java” and “Programming”, respectively.
- After that, apply the “thenCombine()” method to concatenate both the outcomes of CompletableFuture and return them as a final result.
Output
As seen, both the CompletableFuture outcomes are concatenated.
Example 3: Handling Exceptions in CompletableFuture in Java
“CompletableFuture” contains methods such as “exceptionally” and “handle” to handle the exceptions that can occur during asynchronous calculations and returns a fallback value or apply an alternative operation:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> handleExcept = CompletableFuture.supplyAsync(() -> 1 / 0)
.exceptionally(except -> 0);
System.out.println(handleExcept.get());
}}
In this block of code, the “supplyAsync()” method is associated with the lambda function such that “1” is divided by “0” that gives an “ArithmeticException” and the control goes to the “exceptionally” block that handles the exception by retrieving “0” via the “get()” method.
Output
Example 4: Running Multiple Futures in Parallel Using CompletableFuture
“CompletableFuture” can also be utilized to execute multiple futures in parallel utilizing the “allOf()” method:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
return "First Outcome";
});
CompletableFuture<String> secondFuture = CompletableFuture.supplyAsync(() -> {
return "Second Outcome";
});
CompletableFuture<String> thirdFuture = CompletableFuture.supplyAsync(() -> {
return "Third Outcome";
});
CompletableFuture<Void> allFutures = CompletableFuture.allOf(firstFuture, secondFuture, thirdFuture);
allFutures.thenRun(() -> {
String out1 = firstFuture.join();
String out2 = secondFuture.join();
String out3 = thirdFuture.join();
System.out.println(out1 + "-" + out2 + "-" + out3);
});
}}
Based on this snippet of code, perform the below-given steps:
- Create three CompletableFuture instances with the help of the “supplyAsync()” method with each retrieving a String outcome.
- Now, apply the “allOf()” method to combine the specified three futures as a single future.
- It is such that this single future completes when all three complete.
- After that, apply the “thenRun()” method on the instance combining all the futures to notify the action when all three futures have been completed.
- Lastly, the “join()” method is used to get the results of all the futures.
Output
Here, it can be seen that all the futures are linked accordingly.
Example 5: Processing Results of Asynchronous Calculations
The following code demonstration processes the output of a calculation by feeding it to a function. This is done via the “thenAccept()” method that takes a Consumer and passes it the output of the calculation:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<String> x = CompletableFuture.supplyAsync(() -> "Java");
CompletableFuture<Void> result = x.thenAccept(a -> System.out.println("Retrieved Computation -> " + a));
result.get();
}}
In this code, as discussed, the “thenAccept()” method receives a Consumer and the “get()” method gets an instance of the “Void” type.
Output
However, if there is no need to get the calculation value or to return some value at the end of the chain, a Runnable lambda can be passed to the “thenRun()” method, as stated below:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<String> x = CompletableFuture.supplyAsync(() -> "Java");
CompletableFuture<Void> result =
x.thenRun(() -> System.out.println("Computation Ended! "));
result.get();
}}
Output
Here, it can be observed that the calculation value i.e., “Java” is not retrieved.
Example 6: Applying the Async Methods of CompletableFuture
The following example applies some async methods of “CompletableFuture”:
runAsync()
This code implements the “runAsync()” method:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<Void> x = CompletableFuture.runAsync(() -> {
System.out.println("Thread -> " + Thread.currentThread().getName());
System.out.println("Hi from Async Task");
});
}}
Here, the “runAsync()” method runs a task asynchronously without getting any value and the processing is done in a separate thread.
thenComposeAsync()
The below code example uses the “thenComposeAsync()” method:
package jbArticles;
import java.util.concurrent.*;
public class Completable {
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> x = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> y = x.thenComposeAsync(a -> CompletableFuture.supplyAsync(() -> a + 3));
y.thenAccept(out -> System.out.println(out));
}}
Here, two “CompletableFuture” are chained such that the latter “CompletableFuture” takes the outcome of the first one and adds the value “3” to it. Lastly, the “thenAccept()” method displays the outcome of the latter CompletableFuture.
Output
As analyzed, the calculation done by the second “CompletableFuture” is returned accordingly.
What are the Differences Between CompletableFuture and Future?
Both these approaches are used to represent an outcome that will be there in the future. Still, there are the following differences between these approaches:
Feature | CompletableFuture | Future |
---|---|---|
Composition | It has a more powerful composition than Future. | In the Future, it is challenging to combine the results of various operations. |
Blocking/Non-blocking | Future is a non-blocking API. | Future is a blocking API. |
Exception Handling | It has better exceptional handling as compared to the Future. | In this approach, the completion status of the calculation can only be verified. |
Completion | Here, the completion of the future can be controlled. | In this object, the future can not be completed explicitly. |
Conclusion
“CompletableFuture” is a class in the “java.util.concurrent” package that implements the Future and CompletionStage interface. It can be considered as a container containing the output of an asynchronous operation executed on a separate thread.
It can be created and composed in Java. Also, the exceptions can be handled in it and its methods can be used to run a task asynchronously or chain multiple CompletableFuture.