thenApply vs thenCompose vs thenCombine — when do you use… — Cracked Java
// Concurrency & Multithreading · Callable, Future & CompletableFuture
SeniorTheoryCodingBig TechAmazonGoogle

thenApply vs thenCompose vs thenCombine — when do you use each?

Use thenApply for a plain transform (A -> B), thenCompose when the transform itself returns a CompletableFuture (flat-map, A -> CF<B>), and thenCombine to merge two independent futures into one result.

thenApply — map

thenApply takes the result and returns a value. It is map: Function<T, R>.

CompletableFuture<Integer> len =
    CompletableFuture.supplyAsync(() -> "hello")
                     .thenApply(String::length);   // String -> int

If your function returns a CompletableFuture, thenApply will happily wrap it, leaving you with CompletableFuture<CompletableFuture<R>> — a nested mess.

thenCompose — flatMap

thenCompose is for when the next step is itself asynchronous. The function returns a CompletionStage<R>, and thenCompose flattens it: Function<T, CompletionStage<R>>.

CompletableFuture<Profile> profile =
    findUserId(name)                              // CF<Long>
        .thenCompose(id -> loadProfile(id));      // id -> CF<Profile>, flattened
// result is CF<Profile>, NOT CF<CF<Profile>>

Rule of thumb: if your callback returns a future, you want thenCompose. This is the same map vs flatMap distinction you know from Optional and Stream.

thenCombine — zip two independent futures

thenCombine waits for two futures that run in parallel and combines their results with a BiFunction. The two futures do not depend on each other.

CompletableFuture<Price>  price = CompletableFuture.supplyAsync(this::fetchPrice);
CompletableFuture<Double> rate  = CompletableFuture.supplyAsync(this::fetchFxRate);

CompletableFuture<Double> total =
    price.thenCombine(rate, (p, r) -> p.amount() * r);

Both fetchPrice and fetchFxRate run concurrently; the combiner fires once both are done.

thenApply:   [A] ──► f(a) ──► B            (sync transform)

thenCompose: [A] ──► f(a)=>[B] ──► B       (chained async, flattened)

thenCombine: [A] ─┐
                ├─► combine(a,b) ──► C   (two parallel inputs)
           [B] ─┘
Dependency shape of each operator

Mark your status