Mastering CompletableFuture in Spring Boot 3: 50 Real‑World Async Patterns

This article introduces CompletableFuture, explains its core features and ideal use cases, and provides 50 practical Spring Boot 3 examples covering simple async tasks, chaining, exception handling, timeouts, custom thread pools, HTTP requests, and advanced composition techniques.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering CompletableFuture in Spring Boot 3: 50 Real‑World Async Patterns

1. Introduction

What is CompletableFuture? CompletableFuture resides in the JUC package and represents the future result of an asynchronous computation. Unlike the traditional Future, it offers a rich API for building complex async pipelines, supporting operations such as combining multiple futures, handling exceptions, and non‑blocking execution.

Core features of CompletableFuture:

Non‑blocking: Execute tasks without blocking the main thread.

Task chaining: Link multiple asynchronous tasks together.

Exception handling: Provide elegant ways to handle errors.

Future merging: Combine several futures into one.

When to use:

Asynchronous operations: When tasks can run independently without waiting for each other.

I/O operations: Ideal for network calls, file I/O, or database queries that involve latency.

Complex workflows: When multiple dependent or independent tasks must be executed in a specific order.

Why use:

Performance boost: Leverage non‑blocking operations to better utilize system resources and improve responsiveness.

Simplified code: Compared with traditional callbacks, it enables more elegant asynchronous code management.

Graceful exception handling: Built‑in mechanisms handle exceptions that occur during async processing.

Below are 50 common usage scenarios for CompletableFuture.

2. Practical Cases

2.1 Simple asynchronous task

CompletableFuture.runAsync(() -> {
    System.out.println("异步线程执行任务");
});

2.2 Asynchronous task returning a result

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);

2.3 Using thenApply for chaining

CompletableFuture<Integer> future = CompletableFuture
        .supplyAsync(() -> 10)
        .thenApply(result -> result * 2);

2.4 Using thenAccept to process the result

CompletableFuture.supplyAsync(() -> 10)
    .thenAccept(System.out::println);

2.5 thenRun when the result is not needed

CompletableFuture.supplyAsync(() -> 10)
    .thenRun(() -> System.out.println("任务执行完成."));

2.6 Combining two futures with thenCombine

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> ret = f1.thenCombine(f2, Integer::sum);
ret.thenAccept(result -> System.out.println("合并后的结果: " + result));

2.7 Waiting for all futures with allOf

CompletableFuture<Void> allFutures = CompletableFuture.allOf(
    CompletableFuture.supplyAsync(() -> "Task 1"),
    CompletableFuture.supplyAsync(() -> "Task 2")
);
allFutures.join();

2.8 Using anyOf to wait for the first completed future

CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(
    CompletableFuture.supplyAsync(() -> { Thread.sleep(3000); return "Task 1"; }),
    CompletableFuture.supplyAsync(() -> { Thread.sleep(1000); return "Task 2"; })
);
anyFuture.thenAccept(result -> System.out.println("第一个完成的: " + result));

2.9 Handling exceptions with exceptionally

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.err.println(1 / 0);
    return 666;
}).exceptionally(ex -> {
    System.out.println("Exception: " + ex.getMessage());
    return -1;
});
System.err.println(future.get());

2.10 Using handle to process result and exception together

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.err.println(1 / 0);
    return 666;
}).handle((result, ex) -> {
    return (ex != null) ? 0 : result;
});

2.11 thenCompose to chain dependent tasks

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 10)
    .thenCompose(result -> CompletableFuture.supplyAsync(() -> result * 2));

2.12 Asynchronous HTTP request

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("http://localhost:8080/users/1"))
    .build();
CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
future.thenAccept(response -> System.out.println(response.body()));

2.13 Specifying a custom thread pool

ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(2));
CompletableFuture.runAsync(() -> System.out.printf("%s - 执行任务", Thread.currentThread().getName()), executor);

2.14 Chaining multiple futures

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 10)
    .thenApply(r -> r + 5)
    .thenApply(r -> r * 2);
future.thenAccept(r -> System.out.println("最终结果: " + r));

2.15 Manually completing a future

CompletableFuture<Integer> future = new CompletableFuture<>();
future.complete(10);
future.thenAccept(r -> System.out.println("最终结果: " + r));
future.join();

2.16 Setting a timeout for a task

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(2000);
    return 10;
});
future.orTimeout(1, TimeUnit.SECONDS)
    .exceptionally(ex -> { System.out.println("任务执行超时..."); return null; })
    .join();

2.17 Returning a default value on timeout

CompletableFuture.supplyAsync(() -> {
    Thread.sleep(2000);
    return 10;
}).completeOnTimeout(666, 1, TimeUnit.SECONDS)
  .thenAccept(r -> System.out.println("Result: " + r));

2.18 Combining futures with Stream API

List<CompletableFuture<Integer>> futures = List.of(
    CompletableFuture.supplyAsync(() -> 10),
    CompletableFuture.supplyAsync(() -> 20),
    CompletableFuture.supplyAsync(() -> 30)
);
CompletableFuture<Void> combined = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
combined.thenRun(() -> {
    List<Integer> results = futures.stream()
        .map(CompletableFuture::join)
        .toList();
    System.out.println(results);
}).join();

2.19 Delayed completion

CompletableFuture<Integer> future = new CompletableFuture<>();
Executors.newSingleThreadScheduledExecutor().schedule(() -> future.complete(42), 2, TimeUnit.SECONDS);
future.thenAccept(System.out::println).join();

2.20 thenAcceptBoth to combine two results

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
f1.thenAcceptBoth(f2, (a, b) -> System.out.println("合并结果: " + (a + b))).join();

2.21 acceptEither to process the first completed task

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> { Thread.sleep(3000); return 10; });
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> { Thread.sleep(2000); return 20; });
f1.acceptEither(f2, r -> System.out.println("结果: " + r)).join();

2.22 Cancelling a task

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(2000);
    return 10;
});
future.cancel(true);

2.23 Using whenComplete for final processing

CompletableFuture.supplyAsync(() -> 10)
    .whenComplete((result, ex) -> System.out.println("Final result: " + result))
    .join();

2.24 Processing a stream of futures

List<CompletableFuture<Integer>> futures = List.of(
    CompletableFuture.supplyAsync(() -> { Thread.sleep(2000); return 1; }),
    CompletableFuture.supplyAsync(() -> { Thread.sleep(1000); return 2; }),
    CompletableFuture.supplyAsync(() -> { Thread.sleep(3000); return 3; })
);
futures.forEach(f -> f.thenAccept(r -> System.out.println("Result: " + r)));

2.25 completeExceptionally for forced errors

CompletableFuture<Integer> future = new CompletableFuture<>();
future.completeExceptionally(new RuntimeException("任务执行错误"));
future.exceptionally(ex -> { System.out.println("Exception: " + ex.getMessage()); return 0; });

2.26 thenComposeAsync for asynchronous composition

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5)
    .thenComposeAsync(r -> CompletableFuture.supplyAsync(() -> r * 2));
future.thenAccept(r -> System.out.println(r));

2.27 thenCombineAsync to combine tasks asynchronously

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> combined = f1.thenCombineAsync(f2, Integer::sum);
combined.thenAccept(r -> System.out.println(r));

2.28 Blocking wait until completion

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);
Integer result = future.get();
System.out.println("结果: " + result);

2.29 Merging futures of different types

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "Result");
CompletableFuture<String> combined = f1.thenCombine(f2, (num, str) -> num + " " + str);
combined.thenAccept(System.out::println);

2.30 Combining thenCombine and thenCompose

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> finalFuture = f1.thenCombine(f2, Integer::sum)
    .thenCompose(r -> CompletableFuture.supplyAsync(() -> r * 2));
finalFuture.thenAccept(r -> System.out.println(r));

2.31 handleAsync for asynchronous exception handling

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.err.println(1 / 0);
    return 888;
}).handleAsync((result, ex) -> {
    if (ex != null) {
        System.out.println("Handled exception");
        return 0;
    }
    return result;
});

2.32 Waiting for multiple futures with timeout

List<CompletableFuture<Integer>> futures = List.of(
    CompletableFuture.supplyAsync(() -> 10),
    CompletableFuture.supplyAsync(() -> 20)
);
CompletableFuture<Void> combined = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
    .orTimeout(1, TimeUnit.SECONDS);
combined.thenRun(() -> System.out.println("All done or timed out"));
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaCompletableFutureSpring BootAsynchronous Programming
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.