I am late to the party but here are my 2 cents
I am writing here about these three methods—thenApply(), thenCompose(), and thenCombine().. They might seem similar initially, but they serve distinct purposes.
thenApply()CompletableFuture.CompletableFuture with the transformed result.thenApplyAsync).CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hi Buddy")
.thenApply(result -> result + " .How are you?");
future.thenAccept(System.out::println);
thenCompose()CompletableFuture instances into a single one.CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hi")
.thenCompose(result -> CompletableFuture.supplyAsync(() -> result + " .. hows life???"));
future.thenAccept(System.out::println);
Here, thenCompose() chains two asynchronous tasks. The second CompletableFuture depends on the result of the first.
Why flatten? Without thenCompose(), you'd end up with CompletableFuture<CompletableFuture<String>>, which isn't useful.
thenCombine()CompletableFuture results when both are complete.CompletableFuture with the combined result.CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 1);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
combinedFuture.thenAccept(System.out::println); // prints 3
Here, thenCombine() takes the results of two independent tasks (future1 and future2) and combines them into a single result (3).
thenApply(): Transform the result of a single CompletableFuture.
Think: Synchronous transformation.
thenCompose(): Chain dependent asynchronous tasks.
Think: Task B depends on the result of Task A.
thenCombine(): Combine results of two independent CompletableFutures.
Think: Running two tasks in parallel and merging their results.