CompletableFuture.supplyAsync() / thenApply() / join()
A mechanism for writing asynchronous processing in a declarative style (Java 8 and later). You can chain and combine multiple asynchronous tasks, and write error handling as part of a single fluent chain.
Syntax
import java.util.concurrent.*;
// Runs a task asynchronously and returns a result (Supplier).
CompletableFuture<T> cf = CompletableFuture.supplyAsync(() -> /* expression returning type T */);
// Runs a task asynchronously with no return value (Runnable).
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> { /* task */ });
// Transforms the result and passes it to the next stage (takes a Function).
cf.thenApply(result -> /* transformation */);
// Receives the result and returns a new CompletableFuture.
cf.thenCompose(result -> /* expression returning a CompletableFuture */);
// Consumes the result (no return value).
cf.thenAccept(result -> { /* task */ });
// Returns a default value if an exception occurs.
cf.exceptionally(ex -> /* default value */);
// Handles both the result and any exception.
cf.handle((result, ex) -> /* handler */);
// Blocks until the result is available and retrieves it.
T value = cf.join();
T value = cf.get(); // Throws InterruptedException and ExecutionException.
Main Methods
| Method | Description |
|---|---|
| supplyAsync(Supplier) | Runs a supplier asynchronously and returns a CompletableFuture with a result. |
| runAsync(Runnable) | Runs a Runnable asynchronously. Returns CompletableFuture<Void>. |
| thenApply(Function) | Receives the completed result, transforms it, and passes it to the next stage. |
| thenAccept(Consumer) | Receives the completed result and processes it. Passes Void to the next stage. |
| thenCompose(Function) | Receives the result and returns a new CompletableFuture, flattening the chain. |
| exceptionally(Function) | Returns a default value when an exception occurs. |
| handle(BiFunction) | Handles both the result and any exception. Called in both normal and error cases. |
| allOf(futures...) | Waits until all of the given CompletableFuture instances complete. |
| anyOf(futures...) | Proceeds as soon as any one of the given CompletableFuture instances completes. |
| join() | Returns the result. Unlike get(), it does not throw checked exceptions. |
Sample Code
import java.util.concurrent.*;
// Fetch data asynchronously and transform it.
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> {
// Simulates a time-consuming operation (e.g., an API call).
try { Thread.sleep(500); } catch (InterruptedException e) {}
return "hello";
})
.thenApply(String::toUpperCase) // Transforms to "HELLO".
.thenApply(s -> s + " WORLD"); // Transforms to "HELLO WORLD".
System.out.println(cf.join()); // Prints "HELLO WORLD".
// Use exceptionally() to return a default value on error.
CompletableFuture<Integer> safeCf = CompletableFuture
.supplyAsync(() -> {
if (true) throw new RuntimeException("Processing failed.");
return 42;
})
.exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return -1; // Returns a default value.
});
System.out.println(safeCf.join()); // Prints "-1".
// Use allOf() to wait for multiple asynchronous tasks to complete.
CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> 30);
CompletableFuture.allOf(task1, task2, task3).join();
int total = task1.join() + task2.join() + task3.join();
System.out.println("Total: " + total); // Prints "Total: 60".
Notes
Using CompletableFuture lets you combine asynchronous tasks sequentially and in parallel without falling into callback hell. Use thenApply() to transform results, thenCompose() to chain asynchronous tasks (flat map), and handle() as a general-purpose handler that runs in both normal and error cases.
By default, tasks run on the shared ForkJoinPool.commonPool(). To use a custom thread pool, pass an Executor as the second argument, for example: supplyAsync(supplier, executor).
For managing thread pools, see ExecutorService / Executors.newFixedThreadPool().
If you find any errors or copyright issues, please contact us.