0Pricing
Java Academy · Lesson

Chaining with thenApply and thenCompose

Transform results with thenApply and flatMap async steps with thenCompose to avoid nested futures.

Chaining with thenApply and thenCompose is a free Java Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

thenApply: Transform the Result

thenApply(Function) applies a synchronous function to the result of a completed future, returning a new future of the transformed type. It does not start a new async task.

CompletableFuture<String> nameFuture = CompletableFuture
    .supplyAsync(() -> 42L)            // CF<Long>
    .thenApply(id -> "User#" + id);    // CF<String>
System.out.println(nameFuture.join()); // "User#42"

thenApplyAsync: Transform on a Thread

thenApplyAsync runs the transformation function on the ForkJoinPool (or a supplied executor), freeing the completing thread immediately.

CompletableFuture<String> result = fetchUserAsync()
    .thenApplyAsync(user -> serialize(user)); // runs on pool thread

thenAccept: Consume Without Returning

thenAccept(Consumer) processes the result but returns CompletableFuture<Void>. Use it as the last step in a pipeline when you don't need to pass a value forward.

fetchUserAsync()
    .thenApply(User::getName)
    .thenAccept(name -> System.out.println("Hello, " + name));

thenRun: Run After Completion

thenRun(Runnable) runs a task after the future completes, ignoring the result. Good for closing resources or triggering side effects.

downloadFileAsync(url)
    .thenRun(() -> System.out.println("Download complete"));

Chaining Multiple thenApply Calls

Multiple thenApply calls form a pipeline. Each step receives the previous step's output.

CompletableFuture<Double> result = CompletableFuture
    .supplyAsync(() -> "  42.5  ")
    .thenApply(String::trim)
    .thenApply(Double::parseDouble)
    .thenApply(d -> d * 1.21); // apply 21% VAT
System.out.println(result.join()); // 51.425

thenCompose: Flatten Nested Futures

thenCompose(Function<T, CompletionStage<U>>) is like flatMap for futures. It avoids CompletableFuture<CompletableFuture<T>> by flattening the nested future.

// Wrong with thenApply — returns CF<CF<Order>>:
CompletableFuture<CompletableFuture<Order>> nested =
    fetchUser(id).thenApply(u -> fetchLatestOrder(u.getId()));
// Correct with thenCompose — returns CF<Order>:
CompletableFuture<Order> flat =
    fetchUser(id).thenCompose(u -> fetchLatestOrder(u.getId()));

thenCompose for Sequential Async Calls

Use thenCompose when each async step depends on the result of the previous one — making steps sequential and result-dependent.

fetchUser(userId)
    .thenCompose(user -> fetchOrders(user.getId()))
    .thenCompose(orders -> computeTotal(orders))
    .thenAccept(total -> System.out.println("Total: " + total));

thenApply vs thenCompose

thenApply: maps T -> U (synchronous transform). thenCompose: maps T -> CompletableFuture<U> and flattens. Use compose when the next step is also async.

Handling Intermediate Errors with handle

handle(BiFunction<T, Throwable, U>) is called whether the future completed normally or exceptionally, allowing recovery or transformation of errors mid-pipeline.

fetchUser(id)
    .thenApply(User::getProfile)
    .handle((profile, ex) -> {
        if (ex != null) return Profile.empty(); // recover
        return profile;
    })
    .thenAccept(System.out::println);

whenComplete: Side Effect After Any Outcome

whenComplete(BiConsumer) is called after completion (normal or exceptional) for logging or cleanup. Unlike handle, it does not change the result.

fetchAsync()
    .whenComplete((result, ex) -> {
        if (ex != null) log.error("Failed", ex);
        else            log.info("Result: " + result);
    });

Non-Blocking Pipeline Example

Compose a fully non-blocking pipeline: fetch user, fetch their orders, compute summary — all steps run asynchronously, each depending on the previous.

CompletableFuture<String> summary =
    fetchUser(userId)
        .thenCompose(u -> fetchOrders(u.getId()))
        .thenApply(orders -> orders.size() + " orders")
        .exceptionally(ex -> "Error: " + ex.getMessage());
System.out.println(summary.join());

Quick Check

When should you use thenCompose instead of thenApply?

Recap

thenApply transforms sync values in a pipeline. thenCompose chains async steps (flatMap for futures). handle / whenComplete add error handling and side effects. Build pipelines left-to-right for readability.

Frequently asked questions

Is the “Chaining with thenApply and thenCompose” lesson free?

Yes — the full text of “Chaining with thenApply and thenCompose” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.

What will I learn in “Chaining with thenApply and thenCompose”?

Transform results with thenApply and flatMap async steps with thenCompose to avoid nested futures. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Chaining with thenApply and thenCompose” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Creating and Completing CompletableFutures
  2. Chaining with thenApply and thenCompose
  3. Combining Futures: allOf and anyOf
  4. Error Handling and Async HTTP Pipeline
← Back to Java Academy