What is the difference between CompletableFuture's thenCompose and thenApply, and when do you use each?
thenApply(f) takes a synchronous function that transforms T into U and returns a CompletableFuture<U>. If the function you pass to thenApply happens to itself return a CompletableFuture<U>, the result ends up being an unwanted nested CompletableFuture<CompletableFuture<U>>, which is rarely what you actually want. thenCompose(f), by contrast, is designed for exactly that situation: it takes a function from T to CompletableFuture<U> and automatically flattens the result down to a plain CompletableFuture<U>, playing the same role that flatMap plays for Stream. The rule of thumb is simple: if the next step in your chain is a plain synchronous computation, use thenApply; if the next step is itself asynchronous and returns another CompletableFuture, use thenCompose to avoid ending up with a nested future.
Ready to master this question?
Generate a complete walkthrough — background, the full answer in plain language, a working code example explained line by line, a real-world scenario, common mistakes, and how this same question gets asked in different ways.
Sign in to generate a response