Table of contents
1.
Introduction
2.
What is CompletableFuture?
3.
Creating a CompletableFuture
3.1.
1. Using the `supplyAsync` method
3.2.
2. Using the `runAsync` method
3.3.
3. Using the `completedFuture` method
4.
Composing CompletableFuture
4.1.
1. `thenApply`
4.2.
2. `thenAccept`
4.3.
3. `thenCompose`
4.4.
4. `thenCombine`
5.
Handling Exceptions in CompletableFuture
5.1.
1. `exceptionally`
5.2.
2. `handle`
5.3.
3. `completeExceptionally`
6.
Frequently Asked Questions
6.1.
Can CompletableFuture be used for parallel processing?
6.2.
How does CompletableFuture handle timeouts?
6.3.
Is CompletableFuture thread-safe?
7.
Conclusion
Last Updated: Aug 4, 2024
Easy

Completablefuture in Java 8

Author Ravi Khorwal
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

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. 

Completablefuture in Java 8

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.

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 AlgorithmsCompetitive ProgrammingOperating SystemsComputer Networks, DBMSSystem Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.

Live masterclass