SyntaxStudy
Sign Up
Java CompletableFuture for Async Programming
Java Beginner 1 min read

CompletableFuture for Async Programming

CompletableFuture, introduced in Java 8, provides a powerful and flexible asynchronous programming model. Unlike basic Future, a CompletableFuture can be explicitly completed, chained with callbacks, and combined with other futures. It implements both Future and CompletionStage, giving access to a rich set of combination operators. supplyAsync() runs a Supplier asynchronously in the common ForkJoinPool (or a provided executor) and returns a CompletableFuture with the result. Chaining is the defining feature of CompletableFuture. thenApply() transforms the result with a Function (like Stream.map()), thenAccept() consumes the result with a Consumer, and thenRun() executes a Runnable after completion. These are non-blocking — they register callbacks that run when the future completes. thenCompose() (like flatMap()) chains dependent async operations. thenCombine() combines the results of two independent futures with a BiFunction. Error handling is handled by exceptionally() which provides a fallback value if an exception occurs, and handle() which handles both success and failure in a single callback. allOf() waits for all of a collection of futures to complete, and anyOf() completes when any one of them does. This composable style allows you to build complex async workflows without callback hell or blocking threads.
Example
import java.util.concurrent.*;
import java.util.List;

public class CompletableFutureDemo {

    static String fetchUser(int id) throws InterruptedException {
        Thread.sleep(100); // simulate network
        return "User#" + id;
    }

    static String fetchEmail(String user) throws InterruptedException {
        Thread.sleep(80);
        return user.toLowerCase().replace("#", "") + "@example.com";
    }

    public static void main(String[] args) throws Exception {
        // Basic async computation
        CompletableFuture<String> future = CompletableFuture
            .supplyAsync(() -> {
                try { return fetchUser(42); }
                catch (InterruptedException e) { throw new RuntimeException(e); }
            })
            .thenApply(user -> user + " (verified)")
            .thenApply(String::toUpperCase);

        System.out.println("Result: " + future.get());

        // thenCompose: chain dependent async calls
        CompletableFuture<String> emailFuture = CompletableFuture
            .supplyAsync(() -> { try { return fetchUser(7); }
                catch (InterruptedException e) { throw new RuntimeException(e); } })
            .thenCompose(user -> CompletableFuture.supplyAsync(() -> {
                try { return fetchEmail(user); }
                catch (InterruptedException e) { throw new RuntimeException(e); }
            }));
        System.out.println("Email: " + emailFuture.get());

        // allOf: wait for multiple futures
        var f1 = CompletableFuture.supplyAsync(() -> "Result A");
        var f2 = CompletableFuture.supplyAsync(() -> "Result B");
        var f3 = CompletableFuture.supplyAsync(() -> "Result C");

        CompletableFuture.allOf(f1, f2, f3).join();
        System.out.println(f1.get() + ", " + f2.get() + ", " + f3.get());

        // exceptionally: fallback on failure
        CompletableFuture<String> safe = CompletableFuture
            .supplyAsync(() -> { throw new RuntimeException("network error"); })
            .exceptionally(ex -> "Fallback: " + ex.getMessage());
        System.out.println(safe.get());
    }
}