Creating a CompletableFuture
There are several ways to create a CompletableFuture in Java. Few common methods are:
1. Using the `supplyAsync` method
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Perform some task
return "Result";
});
The `supplyAsync` method takes a `Supplier` as a parameter, which is a function that returns a value. It runs the supplier task asynchronously & returns a CompletableFuture that will complete with the supplier's return value.
2. Using the `runAsync` method
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// Perform some task
System.out.println("Task completed");
});
The `runAsync` method is similar to `supplyAsync`, but it takes a `Runnable` as a parameter, which is a function that doesn't return a value. It runs the runnable task asynchronously & returns a CompletableFuture that will complete when the task is finished.
3. Using the `completedFuture` method
CompletableFuture<String> future = CompletableFuture.completedFuture("Result");
The `completedFuture` method returns a CompletableFuture that is already completed with the specified value. This can be useful when you have a value that is immediately available & you want to wrap it in a CompletableFuture.
Once you have a CompletableFuture, you can use methods like `thenApply`, `thenAccept`, `thenRun`, & `thenCompose` to specify what should happen when the future completes.
Composing CompletableFuture
One of the most powerful features of CompletableFuture is the ability to compose multiple futures together. This allows you to create complex asynchronous workflows by chaining together multiple tasks.
Let’s see some of the key methods for composing CompletableFutures:
1. `thenApply`
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = future1.thenApply(result -> result + " World");
The `thenApply` method takes a `Function` as a parameter, which is a function that takes a value & returns a new value. It applies the function to the result of the previous CompletableFuture & returns a new CompletableFuture that will complete with the function's return value.
2. `thenAccept`
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
future.thenAccept(result -> System.out.println(result));
The `thenAccept` method takes a `Consumer` as a parameter, which is a function that takes a value & doesn't return anything. It applies the consumer to the result of the previous CompletableFuture & returns a new CompletableFuture that will complete when the consumer is finished.
3. `thenCompose`
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = future1.thenCompose(result ->
CompletableFuture.supplyAsync(() -> result + " World"));
The `thenCompose` method is similar to `thenApply`, but instead of returning a value, it returns a new CompletableFuture. This allows you to chain together multiple asynchronous tasks, where the result of one task is used as input to the next task.
4. `thenCombine`
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
CompletableFuture<String> future3 = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
The `thenCombine` method takes another CompletableFuture & a `BiFunction` as parameters. The BiFunction is a function that takes two values & returns a new value. It waits for both futures to complete, then applies the BiFunction to their results & returns a new CompletableFuture that will complete with the BiFunction's return value.
Handling Exceptions in CompletableFuture
When working with asynchronous tasks, it's important to be able to handle exceptions that may occur. CompletableFuture provides several methods for handling exceptions, allowing you to create robust & fault-tolerant asynchronous workflows.
Let’s discuss some of the few key methods for handling exceptions in CompletableFuture:
1. `exceptionally`
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (somethingWrong) {
throw new RuntimeException("Something went wrong");
}
return "Result";
}).exceptionally(ex -> "Default Result");
The `exceptionally` method allows you to specify a function that will be called if an exception is thrown during the execution of the CompletableFuture. The function takes the exception as a parameter & returns a default value that will be used as the result of the future.
2. `handle`
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (somethingWrong) {
throw new RuntimeException("Something went wrong");
}
return "Result";
}).handle((result, ex) -> {
if (ex != null) {
// Handle the exception
return "Default Result";
} else {
return result;
}
});
The `handle` method is similar to `exceptionally`, but it allows you to handle both the normal result & the exception in a single function. The function takes two parameters: the result of the future (if it completed normally) & the exception (if one was thrown). You can then use an if-else statement to handle the two cases separately.
3. `completeExceptionally`
CompletableFuture<String> future = new CompletableFuture<>();
// ...
if (somethingWrong) {
future.completeExceptionally(new RuntimeException("Something went wrong"));
}
The `completeExceptionally` method allows you to manually complete a future with an exception. This can be useful if you need to signal an error condition from outside the future's task.
Frequently Asked Questions
Can CompletableFuture be used for parallel processing?
Yes, CompletableFuture can be used for parallel processing by using the thenCombine or allOf methods to execute multiple tasks concurrently & combine their results.
How does CompletableFuture handle timeouts?
CompletableFuture does not have built-in support for timeouts, but you can use the orTimeout method to specify a timeout duration & provide a default value if the timeout is exceeded.
Is CompletableFuture thread-safe?
Yes, CompletableFuture is thread-safe & can be safely used in multi-threaded environments without the need for explicit synchronization.
Conclusion
In this article, we have learned about CompletableFuture, a powerful tool introduced in Java 8 for writing asynchronous & non-blocking code. We explained how to create CompletableFutures using methods like supplyAsync, runAsync, & completedFuture, & how to compose multiple futures together using methods like thenApply, thenCompose, & thenCombine. We also discussed how to handle exceptions in CompletableFuture with the help of methods like exceptionally, handle, & completeExceptionally.
You can also practice coding questions commonly asked in interviews on Coding Ninjas Code360.
Also, check out some of the Guided Paths on topics such as Data Structure and Algorithms, Competitive Programming, Operating Systems, Computer Networks, DBMS, System Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.