Completable Future in Java: A Comprehensive Guide

Naveen Metta
5 min readMay 8, 2023

--

Java has been around for over two decades, and it still remains one of the most popular programming languages in the world. One of the key features of Java is its ability to handle asynchronous programming with ease. The Completable Future API is one of the most powerful tools in Java’s arsenal for asynchronous programming. In this guide, we’ll explore what Completable Future is, how it works, and how it can be used to write better and more efficient code.

What is Completable Future in Java?

Completable Future is a Java API that was introduced in Java 8. It is designed to handle asynchronous tasks and provides a flexible and powerful framework for managing concurrent operations. Completable Future is part of the Java Concurrency API, which is a set of tools and classes that allow developers to create concurrent and parallel applications.

Completable Future is a class that represents a computation that can be completed asynchronously. It is similar to the Future class in Java, but with some added functionality. A Completable Future can be completed with a value or an exception, and it can also be used to combine multiple Completable Futures into a single result.

How does Completable Future work?

Completable Future works by allowing developers to define a task that will be completed asynchronously. This task can be any operation that takes time to complete, such as network I/O, file I/O, or computation. The task is then submitted to a Completable Future object, which represents the future completion of the task.

Completable Future provides a rich set of methods for managing the completion of the task. These methods allow developers to specify what should happen when the task is complete, whether it completed successfully or with an exception.

Completable Future also provides a way to chain multiple Completable Futures together. This is done using the thenCompose() method, which allows developers to specify a function that will be executed when the first task is complete. The output of the first task is then used as the input to the second task, and so on.

How to use Completable Future

Using Completable Future in Java is relatively straightforward. The first step is to define the task that will be completed asynchronously. This is done by creating a Supplier object that represents the task. The Supplier object takes no arguments and returns a result of type T. Here’s an example of a simple task that generates a random number:

Supplier<Integer> task = () -> {
Random rand = new Random();
return rand.nextInt(100);
};

The next step is to create a Completable Future object that represents the future completion of the task. This is done using the CompletableFuture.supplyAsync() method, which takes the Supplier object as an argument. Here’s an example:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(task);

Once the Completable Future object is created, developers can use the various methods provided by Completable Future to manage the completion of the task. For example, they can use the thenApply() method to specify a function that will be executed when the task is complete. Here’s an example:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(task);
CompletableFuture<String> future2 = future.thenApply(result -> "Result: " + result);

In this example, the thenApply() method takes a function that takes the result of the task as input and returns a String. The output of the function is used as the completion value for the second Completable Future object.

Another useful method provided by Completable Future is thenCompose(), which allows developers to chain multiple Completable Futures together. Here’s an example:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(task);
CompletableFuture<String> future2 = future.thenCompose(result -> CompletableFuture.supplyAsync(() -> {
return "Result: " + result;
});

In this example, the thenCompose() method takes a function that takes the result of the first task as input and returns a new Completable Future object. The output of this Completable Future object is used as the completion value for the second Completable Future object.

Completable Future also provides a way to handle exceptions that occur during the task. This is done using the exceptionally() method, which takes a function that will be executed if an exception occurs during the task. Here’s an example:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(task);
CompletableFuture<String> future2 = future.thenApply(result -> {
if (result > 50) {
throw new RuntimeException("Result too large!");
}
return "Result: " + result;
}).exceptionally(ex -> {
return "Exception: " + ex.getMessage();
});

In this example, the thenApply() method checks if the result of the task is greater than 50. If it is, a RuntimeException is thrown. The exceptionally() method takes a function that will be executed if an exception occurs during the task. In this case, the function simply returns a String with the exception message.

Completable Future also provides methods for combining multiple Completable Futures into a single result. The most commonly used method for this is thenCombine(), which takes two Completable Future objects as input and a function that combines the results of the two tasks. Here’s an example:

Supplier<Integer> task1 = () -> {
Random rand = new Random();
return rand.nextInt(100);
};

Supplier<Integer> task2 = () -> {
Random rand = new Random();
return rand.nextInt(100);
};

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(task1);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(task2);

CompletableFuture<Integer> combined = future1.thenCombine(future2, (result1, result2) -> {
return result1 + result2;
});

In this example, two separate Completable Future objects are created, each representing a task that generates a random number. The thenCombine() method is used to combine the two Completable Future objects into a single Completable Future object that represents the sum of the two random numbers.

Benefits of using Completable Future

Using Completable Future in Java offers several benefits over traditional approaches to asynchronous programming. Some of these benefits include:

1. Cleaner and more concise code: Completable Future allows developers to write asynchronous code that is easier to read and understand. The chaining of multiple Completable Futures together is done using simple and easy-to-understand methods.

2. Better error handling: Completable Future provides a way to handle exceptions that occur during the task. This makes it easier to write code that is more robust and less likely to fail due to unexpected errors.

3. Improved performance: Completable Future allows developers to execute multiple tasks concurrently, which can lead to improved performance and faster execution times.

4. More control over execution: Completable Future allows developers to control the execution of asynchronous tasks in a more fine-grained manner. For example, they can specify a timeout for the task, or cancel the task if it is taking too long to complete.

Conclusion

Completable Future is a powerful tool for asynchronous programming in Java. It allows developers to write cleaner, more concise, and more efficient code that is easier to read and understand. Completable Future provides a flexible and powerful framework for managing concurrent operations, and it is an essential part of the Java Concurrency API. By mastering Completable Future, developers can write better and more efficient code that can take full advantage of the power of Java’s concurrency features.

--

--

Naveen Metta

I'm a Full Stack Developer with 2.5 years of experience. feel free to reach out for any help : mettanaveen701@gmail.com