Do you think IIT Guwahati certified course can help you in your career?
No
Introduction
CompletableFuture is an important feature introduced in Java 8 that allows us to write asynchronous & non-blocking code. It provides a way to handle tasks that may take a long time to complete without blocking the main thread. CompletableFuture lets us chain together multiple tasks, handle exceptions, & combine results from multiple asynchronous operations.
In this article, we will learn what CompletableFuture is, how to create & use it, how to compose multiple CompletableFutures, & how to handle exceptions.
What is CompletableFuture?
CompletableFuture is a class in Java 8 that represents a future result of an asynchronous task. It allows us to write code that can execute tasks in the background without blocking the main thread. The main thread can continue doing other work while the CompletableFuture is running, & it can be notified when the task is complete.
CompletableFuture implements the Future interface, which represents the result of an asynchronous computation. However, CompletableFuture goes beyond the basic Future by providing a rich set of methods for composing, combining, & handling errors in asynchronous tasks.
One of the key features of CompletableFuture is that it allows us to chain together multiple asynchronous tasks. We can specify what should happen when a task completes, & we can even use the result of one task as input to another task. This makes it easy to create complex asynchronous workflows without having to manage a lot of low-level threading code.
Why Use CompletableFuture in Java 8?
CompletableFuture simplifies asynchronous programming by enabling non-blocking execution and smoother task management.
Imagine ordering food online. With a traditional thread (Future), you place the order and wait idly. With CompletableFuture, you place the order and continue watching your show; you get notified when the food arrives. Similarly, in an e-commerce app, CompletableFuture lets you process payments, inventory checks, and shipping in parallel—without pausing the main thread.
Key Benefits:
Non-blocking execution frees up threads for other work.
Improves performance in concurrent systems.
Supports chaining and combining tasks seamlessly.
Scales better in high-load or reactive applications.
Use CompletableFuture when you need efficient, readable, and non-blocking asynchronous logic in Java.
CompletableFuture vs Future: What’s the Difference?
Feature
Future
CompletableFuture
Blocking Behavior
get() blocks the thread
Supports non-blocking callbacks
Chaining Support
Not supported
Supports chaining via thenApply(), etc.
Exception Handling
Must use try-catch manually
Built-in handling via exceptionally()
Combining Tasks
Requires custom logic
Supports allOf() and anyOf() directly
1. Blocking vs Non-blocking
// Future (blocking)
Future<String> future = executor.submit(() -> "Hello");
String result = future.get(); // Blocks until result is available
// CompletableFuture (non-blocking)
CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(System.out::println); // Runs asynchronously
2. Chaining Support
CompletableFuture.supplyAsync(() -> "Rahul")
.thenApply(name -> name + " Mishra")
.thenAccept(System.out::println);
3. Exception Handling
Future requires explicit try-catch.
CompletableFuture handles exceptions with .exceptionally() or .handle() methods, making error recovery cleaner.
4. Combining Multiple Async Tasks
Future requires manual coordination.
CompletableFuture uses allOf() to run tasks in parallel and wait for all to complete, or anyOf() to proceed when the first one finishes.
Creating a CompletableFuture
There are several ways to create a CompletableFuture in Java. Few common methods are:
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.
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.
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:
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.
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.
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.
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.