java 128 lines · 3 tabs

Coordinate Parallel Remote Lookups With CompletableFuture in Java

Shared by codesnips Jul 2026
3 tabs
public class ProductAggregator implements AutoCloseable {

    private final RemoteServices services;
    private final ExecutorService pool = Executors.newFixedThreadPool(8);

    public ProductAggregator(RemoteServices services) {
        this.services = services;
    }

    public CompletableFuture<ProductView> load(String productId) {
        CompletableFuture<Price> price = CompletableFuture
            .supplyAsync(() -> services.fetchPrice(productId), pool)
            .orTimeout(400, TimeUnit.MILLISECONDS)
            .exceptionally(ex -> Price.unavailable(productId));

        CompletableFuture<Inventory> inventory = CompletableFuture
            .supplyAsync(() -> services.fetchInventory(productId), pool)
            .orTimeout(400, TimeUnit.MILLISECONDS)
            .exceptionally(ex -> Inventory.unknown(productId));

        CompletableFuture<List<Review>> reviews = CompletableFuture
            .supplyAsync(() -> services.fetchReviews(productId), pool)
            .orTimeout(600, TimeUnit.MILLISECONDS)
            .exceptionally(ex -> Collections.emptyList());

        return combineAll(productId, price, inventory, reviews);
    }

    private CompletableFuture<ProductView> combineAll(String productId,
                                                     CompletableFuture<Price> price,
                                                     CompletableFuture<Inventory> inventory,
                                                     CompletableFuture<List<Review>> reviews) {
        return CompletableFuture
            .allOf(price, inventory, reviews)
            .thenApply(ignored -> new ProductView(
                productId,
                price.join(),
                inventory.join(),
                reviews.join()));
    }

    @Override
    public void close() {
        pool.shutdown();
    }
}
3 files · java Explain with highlit

This snippet shows how to fan out several independent remote calls in parallel and combine their results into a single response using CompletableFuture, without blocking a thread per call. The example builds a product detail page that needs pricing, inventory, and review data from three separate services, each of which can be slow or fail on its own.

In ProductAggregator, each dependency is launched with CompletableFuture.supplyAsync, running on a bounded executor rather than the common ForkJoinPool, which is important because the work is IO-bound and the common pool is shared across the JVM. The three futures start immediately and run concurrently; the code only waits once, at the join point. orTimeout bounds each call so one hung service cannot stall the whole aggregation, and exceptionally converts a failure into a safe fallback value so a single degraded dependency does not fail the entire page. This is the resilience trade-off: partial data is preferred over no data.

The combination happens in combineAll, which uses CompletableFuture.allOf to produce a future that completes only when every input has settled. Because each individual future already handled its own timeout and error, the join calls inside the final thenApply never block for long and never throw — they simply read the already-computed values. This separation, per-future resilience first, then aggregation, is what keeps the combining logic clean.

RemoteServices wraps the JDK HttpClient and returns futures directly via sendAsync, so no thread is parked waiting on the network. The service methods also demonstrate mapping a raw HTTP body into a domain object inside thenApply, keeping parsing off the calling thread.

A key pitfall the code avoids is calling join on each future sequentially, which would serialize the calls and defeat the purpose. Another is forgetting to shut the executor down; ProductAggregator exposes close so the pool is released. This pattern fits any read path that gathers from multiple sources, such as dashboards, search enrichment, or checkout summaries, where latency is dominated by the slowest dependency rather than the sum of them.


Related snips

Share this code

Here's the card — post it anywhere.

Coordinate Parallel Remote Lookups With CompletableFuture in Java — share card
Link copied