Boost Spring Boot API Calls: From Synchronous to CompletableFuture

This article demonstrates how to improve Spring Boot order‑creation API calls by moving from sequential RestTemplate requests to multithreaded, thread‑pooled, CompletionService and CompletableFuture approaches, comparing performance and highlighting best practices for high‑concurrency scenarios.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Boost Spring Boot API Calls: From Synchronous to CompletableFuture

Environment: Spring Boot 2.5.12.

Scenario: calling three third‑party services (user, storage, discount) when creating an order.

Steps: 1) query user info, 2) query inventory, 3) query discount.

Synchronous sequential calls

public boolean createOrder() {
    long start = System.currentTimeMillis();
    String userResult = restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1});
    String storageResult = restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1});
    String discountResult = restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[]{1});
    // 这里合并请求结果处理
    System.out.println(Arrays.toString(new String[]{userResult, storageResult, discountResult}));
    System.out.println("传统方式耗时:" + (System.currentTimeMillis() - start) + "毫秒");
    return true;
}
@GetMapping("/create")
public Object create() {
    return os.createOrder();
}

Result: each request is made one after another, leading to high latency.

Multithreaded with Callable + Future

public boolean createOrder2() {
    long start = System.currentTimeMillis();
    Callable<String> userCallable = () -> {
        return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1});
    };
    Callable<String> storageCallable = () -> {
        return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1});
    };
    Callable<String> discountCallable = () -> {
        return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[]{1});
    };
    FutureTask<String> userTask = new FutureTask<>(userCallable);
    FutureTask<String> storageTask = new FutureTask<>(storageCallable);
    FutureTask<String> discountTask = new FutureTask<>(discountCallable);
    new Thread(userTask).start();
    new Thread(storageTask).start();
    new Thread(discountTask).start();
    try {
        String userResult = userTask.get();
        String storageResult = storageTask.get();
        String discountResult = discountTask.get();
        // 这里合并请求结果处理
        System.out.println(Arrays.toString(new String[]{userResult, storageResult, discountResult}));
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    System.out.println("多线程方式耗时:" + (System.currentTimeMillis() - start) + "毫秒");
    return true;
}

Result: latency reduced, but creating raw threads is discouraged in high‑concurrency environments because it may cause OOM.

ThreadPoolExecutor (Callable + Future)

ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000));
public boolean createOrder3() {
    long start = System.currentTimeMillis();
    List<Future<String>> results = new ArrayList<>(3);
    results.add(pool.submit(() -> {
        return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1});
    }));
    results.add(pool.submit(() -> {
        return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1});
    }));
    results.add(pool.submit(() -> {
        return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[]{1});
    }));
    for (int i = 0, size = results.size(); i < size; i++) {
        try {
            System.out.println(results.get(i).get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
    System.out.println("线程池方式耗时:" + (System.currentTimeMillis() - start) + "毫秒");
    return true;
}

Result: similar latency to the previous method; using a thread pool avoids uncontrolled thread creation.

CompletionService

public boolean createOrder4() {
    long start = System.currentTimeMillis();
    CompletionService<String> cs = new ExecutorCompletionService<>(pool);
    cs.submit(() -> {
        return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1});
    });
    cs.submit(() -> {
        return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1});
    });
    cs.submit(() -> {
        return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[]{1});
    });
    for (int i = 2; i >= 0; i--) {
        try {
            System.out.println(cs.take().get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
    System.out.println("CompletionService方式耗时:" + (System.currentTimeMillis() - start) + "毫秒");
    return true;
}

Result: tasks can be retrieved in the order they finish, improving efficiency; the take() method blocks until a task completes.

CompletableFuture (Java 8)

public boolean createOrder5() {
    long start = System.currentTimeMillis();
    CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
        return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1});
    });
    CompletableFuture<String> storageFuture = CompletableFuture.supplyAsync(() -> {
        return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1});
    });
    CompletableFuture<String> discountFuture = CompletableFuture.supplyAsync(() -> {
        return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[]{1});
    });
    CompletableFuture<List<String>> result = CompletableFuture
        .allOf(userFuture, storageFuture, discountFuture)
        .thenApply(v -> {
            List<String> datas = new ArrayList<>();
            try {
                datas.add(userFuture.get());
                datas.add(storageFuture.get());
                datas.add(discountFuture.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
            return datas;
        })
        .exceptionally(e -> {
            e.printStackTrace();
            return null;
        });
    try {
        System.out.println(result.get());
    } catch (InterruptedException | ExecutionException e1) {
        e1.printStackTrace();
    }
    System.out.println("CompletableFuture方式耗时:" + (System.currentTimeMillis() - start) + "毫秒");
    return true;
}

Result: provides powerful asynchronous composition, similar to JavaScript Promise, and yields the best performance among the examples.

Conclusion: For high‑throughput Spring Boot services, prefer CompletableFuture or CompletionService with a managed thread pool to achieve optimal latency and resource usage.

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.

AsynchronousCompletableFutureSpring BootmultithreadingAPI
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.