How to run async tasks and combine their results
Short answer
supplyAsync() runs a task in the background, thenApply() says what to do with the result, and join() waits for the outcome:
void main() {
CompletableFuture<String> greeting = CompletableFuture
.supplyAsync(() -> "anna")
.thenApply(String::toUpperCase)
.thenApply(name -> "Hello, " + name);
IO.println(greeting.join());
}Output:
Hello, ANNACombining two results
thenCombine() waits for two independent tasks and combines their results:
int load(String what, int value) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return value;
}
void main() {
var price = CompletableFuture.supplyAsync(() -> load("price", 120));
var discount = CompletableFuture.supplyAsync(() -> load("discount", 20));
IO.println(price.thenCombine(discount, (p, d) -> p - d).join());
}Output:
100Waiting for all
allOf() completes when all tasks are done:
void main() {
var futures = List.of(
CompletableFuture.supplyAsync(() -> 1),
CompletableFuture.supplyAsync(() -> 2),
CompletableFuture.supplyAsync(() -> 3));
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
IO.println(futures.stream().mapToInt(CompletableFuture::join).sum());
}Output:
6Handling errors
exceptionally() supplies a value if an error happened at some step:
void main() {
int value = CompletableFuture.supplyAsync(() -> Integer.parseInt("x"))
.exceptionally(error -> {
IO.println("Failed: " + error.getCause().getMessage());
return 0;
})
.join();
IO.println(value);
}Output:
Failed: For input string: "x"
0Common mistake
Forgetting to wait for the result. The program ends, and background tasks from the common pool do not get a chance to finish — their output simply disappears:
void main() {
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
IO.println("never printed");
});
IO.println("main finished");
}Output:
main finishedCall join() or get() if you need the result before the program exits.
Functions used in this recipe
- CompletableFuture.supplyAsync() — Runs a computation asynchronously and returns a `CompletableFuture`.
- CompletableFuture.thenApply() — Adds a step that transforms the result once it is ready.
- CompletableFuture.thenCombine() — Combines the results of two independent tasks once both are ready.
- CompletableFuture.join() — Waits for the result of a `CompletableFuture`; errors arrive as unchecked exceptions.