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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
