CompletableFuture
in Java is a powerful class introduced in Java 8 as part of the java.util.concurrent
package. It represents a Future that can be completed manually and can also be used to build asynchronous pipelines and complex computations. It allows for non-blocking asynchronous programming, where tasks can be executed concurrently and combined with ease.
Key Concepts and Benefits of CompletableFuture
- Asynchronous Execution: Run tasks asynchronously without blocking the main thread.
- Chaining and Composition: Chain multiple asynchronous tasks together, handling dependencies.
- Combining Tasks: Combine multiple
CompletableFuture
tasks. - Exception Handling: Handle exceptions and recover gracefully within asynchronous pipelines.
Creating and Using CompletableFuture
- Creating a CompletableFuture
CompletableFuture future = new CompletableFuture<>();
A simpler way to create a completed future is by using:
CompletableFuture completedFuture = CompletableFuture.completedFuture("result");
- Running Asynchronous Tasks
- Supply a Value Asynchronously:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello, World!";
});
- Run a Task Asynchronously:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Running in a separate thread");
});
- Chaining Tasks (thenApply, thenAccept, thenRun)
- thenApply: Transforms the result of a computation (non-blocking).
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(result -> result + " World!");
- thenAccept: Consumes the result of a computation without returning any value.
future.thenAccept(result -> System.out.println("Result: " + result));
- thenRun: Runs a task after the completion of a
CompletableFuture
but doesn’t receive its result.
future.thenRun(() -> System.out.println("Computation finished"));
- Combining Multiple CompletableFutures
- thenCombine: Combines the results of two
CompletableFutures
.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
combinedFuture.thenAccept(System.out::println); // Output: Hello World
- allOf: Waits for all the
CompletableFutures
to complete.
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
allFutures.thenRun(() -> System.out.println("All futures completed!"));
- anyOf: Returns as soon as any of the
CompletableFutures
completes.
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2);
anyOfFuture.thenAccept(result -> System.out.println("First completed result: " + result));
- Exception Handling
- exceptionally: Handles exceptions thrown during computation.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Something went wrong");
return "Hello";
}).handle((result, ex) -> {
if (ex != null) {
return "Recovered from error: " + ex.getMessage();
}
return result;
});
Complete Example
Here’s an example showing CompletableFuture
with chaining, combining, and exception handling:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(result -> result + " World")
.thenCombine(CompletableFuture.supplyAsync(() -> "from Java"), (s1, s2) -> s1 + " " + s2)
.thenApply(String::toUpperCase)
.exceptionally(ex -> "Exception occurred: " + ex.getMessage());
future.thenAccept(System.out::println); // Output: HELLO WORLD FROM JAVA
}
}
Summary
CompletableFuture
provides a flexible, non-blocking way to perform asynchronous programming in Java. It allows chaining and composing multiple tasks, handling errors gracefully, and executing complex workflows without locking up the main thread. The API is rich and well-suited for scalable, efficient concurrent applications.