CompletableFuture is a powerful tool in Java to implement asynchronous programming. It allows you to perform tasks in the background, chain multiple async tasks together, handle both success and failure scenarios, and combine multiple async computations.
Here’s a summary of how to use CompletableFuture with examples:
1. Run a task asynchronously
Use supplyAsync() (if the task produces a result) or runAsync() (if the task doesn’t return anything).
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Running task in background...");
});
Or with a result:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello, World!";
});
2. Process the result
You can process the result of a CompletableFuture using methods like thenAccept or thenApply.
future.thenApply(result -> {
System.out.println("Received result: " + result);
return result.toUpperCase();
}).thenAccept(uppercaseResult -> {
System.out.println("Transformed result: " + uppercaseResult);
});
3. Combine multiple tasks
You can run multiple tasks in parallel and combine their results.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task2");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (task1Result, task2Result) -> {
return task1Result + " and " + task2Result;
});
combinedFuture.thenAccept(result -> {
System.out.println("Combined Result: " + result);
});
4. Wait for all tasks
If you have multiple tasks and want to wait for all of them to complete, use CompletableFuture.allOf.
CompletableFuture<Void> allTasks = CompletableFuture.allOf(
CompletableFuture.runAsync(() -> System.out.println("Task 1 completed")),
CompletableFuture.runAsync(() -> System.out.println("Task 2 completed"))
);
allTasks.join(); // Blocks the thread and waits for completion
System.out.println("All tasks completed.");
5. Handle errors
You can handle exceptions in async tasks using exceptionally().
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
throw new RuntimeException("Oops, something went wrong!");
}).exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return null;
});
6. Compose dependent tasks
Use thenCompose to chain dependent tasks where the second task depends on the result of the first one.
CompletableFuture.supplyAsync(() -> "Task 1 Result")
.thenCompose(result -> CompletableFuture.supplyAsync(() -> result + " Task 2 Result"))
.thenAccept(finalResult -> System.out.println("Final Result: " + finalResult));
7. Custom Executor
By default, CompletableFuture uses the ForkJoinPool.commonPool for async tasks. You can provide a custom executor for better control over threads.
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {
System.out.println("Running task on custom executor");
}, executor);
Key Methods in CompletableFuture
| Method | Description |
|---|---|
runAsync |
Run a task asynchronously (does not return result). |
supplyAsync |
Run a task asynchronously and return a result. |
thenApply |
Transform the result of a CompletableFuture. |
thenAccept |
Consumes the result of a CompletableFuture (no further processing). |
thenCompose |
Chains dependent tasks where the next uses the result of the previous. |
thenCombine |
Combines two CompletableFuture results. |
allOf / anyOf |
Wait for all or any of multiple futures to complete. |
exceptionally |
Handle exceptions thrown during the async computation. |
join / get |
Block and wait for the task to complete and retrieve its result (not recommended for non-blocking). |
Example Workflow with Pipeline
Here’s an example pipeline:
- Fetch data
- Process it
- Save it
- Notify the user
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture.supplyAsync(() -> {
System.out.println("Fetching data...");
return "Raw Data";
}, executor).thenApply(data -> {
System.out.println("Processing data...");
return data.toUpperCase();
}).thenAccept(processedData -> {
System.out.println("Saving: " + processedData);
}).thenRun(() -> {
System.out.println("Notification: Done!");
}).exceptionally(ex -> {
System.err.println("Pipeline failed due to: " + ex.getMessage());
return null;
}).join();
Using CompletableFuture, you can build flexible, high-performance, and non-blocking applications.
