Backend Development 10 min read

Using CompletableFuture for Parallel REST Calls in Java

The article explains why serial REST calls cause performance bottlenecks, illustrates the benefits of concurrent requests, and demonstrates how Java 8's CompletableFuture can be used to implement parallel REST calls with robust exception handling, improving throughput and resource utilization.

FunTester
FunTester
FunTester
Using CompletableFuture for Parallel REST Calls in Java

In modern application development, parallel processing is likened to a well‑coordinated kitchen where multiple chefs work simultaneously, increasing throughput and reducing response time compared to a single‑threaded approach.

Performance bottleneck of serial calls – In a traditional serial model each request must wait for the previous one to finish, leading to low efficiency, cumulative latency, and vulnerability to network delays or slow endpoints.

Necessity of concurrent calls – REST architecture enables independent endpoints to be invoked in parallel, fully utilizing CPU and network resources, shortening overall waiting time and improving user experience.

Typical scenarios include aggregating data from multiple services, executing independent tasks, and handling time‑critical business flows.

Implementing parallel REST calls with CompletableFuture

package com.funtester.example;

import java.net.URI;
import java.net.http.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class ParallelRestCalls {
    // 创建一个 HttpClient 实例,用于发送 HTTP 请求
    private static final HttpClient client = HttpClient.newHttpClient();

    /**
     * fetch 方法:用于异步请求一个 URL 并返回响应体的内容
     *
     * @param url 要请求的 URL
     * @return 一个 CompletableFuture,包含 HTTP 请求的响应体
     */
    public static CompletableFuture
fetch(String url) {
        // 构建一个 HttpRequest 对象,指定请求的 URI
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .build();

        // 异步发送请求,并返回响应体的内容
        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 定义一个包含多个 URL 的列表,这些 URL 将用于并行发送请求
        List
urls = Arrays.asList(
                "https://api.funtester.com/resource1", // 第一个资源
                "https://api.funtester.com/resource2", // 第二个资源
                "https://api.funtester.com/resource3" // 第三个资源
        );

        // 使用流操作并行地发出请求
        List
> futures = urls.stream()
                .map(ParallelRestCalls::fetch)
                .collect(Collectors.toList());

        // 等待所有请求都完成,这里使用 CompletableFuture.allOf 来处理多个异步任务
        CompletableFuture
allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        // 当所有请求都完成后,将所有响应收集到一个列表中
        CompletableFuture
> allResponses = allOf.thenApply(v ->
                futures.stream()
                        .map(CompletableFuture::join)
                        .collect(Collectors.toList())
        );

        // 获取所有响应的结果,并打印输出
        List
responses = allResponses.get();
        responses.forEach(System.out::println);
    }
}

The above code creates a shared HttpClient, defines a fetch method that returns a CompletableFuture<String> , and uses a stream of URLs to launch asynchronous requests, waiting for all of them with CompletableFuture.allOf before collecting the responses.

Exception handling mechanisms

Solution 1: using exceptionally

/**
 * 异步发送 HTTP 请求并返回响应体的内容。
 *
 * @param url 要请求的 URL 地址
 * @return 一个 CompletableFuture,包含 HTTP 请求的响应体内容,若发生异常,返回错误信息
 */
public static CompletableFuture
fetch(String url) {
    // 创建一个 HttpRequest 对象,构建请求并指定请求的 URL
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .build();

    // 异步发送请求,处理响应并返回响应体内容
    return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .exceptionally(ex -> "Error: " + ex.getMessage());
}

Solution 2: using handle

/**
 * 异步发送 HTTP 请求并返回响应体的内容。如果发生异常,返回错误信息。
 *
 * @param url 要请求的 URL 地址
 * @return 一个 CompletableFuture,包含 HTTP 请求的响应体内容;若发生异常,返回错误信息
 */
public static CompletableFuture
fetch(String url) {
    // 创建一个 HttpRequest 对象,构建请求并指定请求的 URL
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .build();

    // 异步发送请求,处理响应并返回响应体内容,或者捕获异常
    return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .handle((response, ex) -> {
                if (ex != null) {
                    return "Error: " + ex.getMessage();
                }
                return response.body();
            });
}

Both approaches ensure that a value (either the response body or an error message) is returned regardless of success or failure.

Technical advantages summary

Significantly increased system throughput : parallel tasks behave like multiple chefs working on separate burners, boosting overall output.

Optimized resource utilization : threads are efficiently scheduled, preventing CPU and network idle time.

Flexible exception handling : CompletableFuture’s exceptionally and handle methods allow graceful error recovery.

Cleaner, maintainable code : the asynchronous composition is more concise than traditional callbacks or manual thread management.

By applying parallel processing techniques, developers can improve response speed while maintaining stability and security, delivering a more efficient service to end users.

JavaConcurrencyCompletableFutureRESTParallelism
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login 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.