Boost Spring Boot Order Calls: Sync vs Async with ThreadPool & CompletableFuture
This article compares synchronous and various asynchronous approaches for invoking multiple third‑party services in a Spring Boot order‑creation workflow, demonstrating plain sequential calls, manual thread creation, thread‑pool execution, CompletionService handling, and CompletableFuture composition, and shows their performance impact through timing results.
Environment: springboot2.3.9.RELEASE
The typical order‑creation process involves three external calls: query user information, query inventory information, and query discount information.
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});
// merge results
System.out.println(Arrays.toString(new String[]{userResult, storageResult, discountResult}));
System.out.println("Traditional time: " + (System.currentTimeMillis() - start) + " ms");
return true;
}
@GetMapping("/create")
public Object create() {
return os.createOrder();
}Calling the interfaces one by one is very time‑consuming.
Multi‑Thread (Callable + Future)
public boolean createOrder2() {
long start = System.currentTimeMillis();
Callable<String> userCallable = () -> restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1});
Callable<String> storageCallable = () -> restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1});
Callable<String> discountCallable = () -> 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();
// merge results
System.out.println(Arrays.toString(new String[]{userResult, storageResult, discountResult}));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("Multi‑thread time: " + (System.currentTimeMillis() - start) + " ms");
return true;
}The execution time is reduced, showing clear performance improvement, but directly creating threads is discouraged in production because high concurrency can lead to OOM errors.
ThreadPool (Callable + Future) to Prevent OOM
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(() -> restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1})));
results.add(pool.submit(() -> restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1})));
results.add(pool.submit(() -> 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("ThreadPool time: " + (System.currentTimeMillis() - start) + " ms");
return true;
}The timing is similar to the previous method; however, using Future still requires retrieving results sequentially, which can block even if other tasks finish earlier.
CompletionService (Asynchronous Tasks with Result Decoupling)
public boolean createOrder4() {
long start = System.currentTimeMillis();
CompletionService<String> cs = new ExecutorCompletionService<>(pool);
cs.submit(() -> restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1}));
cs.submit(() -> restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1}));
cs.submit(() -> 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 time: " + (System.currentTimeMillis() - start) + " ms");
return true;
}Using CompletionService allows results to be retrieved as soon as any task finishes, regardless of submission order; the take() method blocks until a task completes.
CompletableFuture (Async Composition, JDK 1.8)
public boolean createOrder5() {
long start = System.currentTimeMillis();
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[]{1})
);
CompletableFuture<String> storageFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[]{1})
);
CompletableFuture<String> discountFuture = CompletableFuture.supplyAsync(() ->
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 e) {
e.printStackTrace();
}
System.out.println("CompletableFuture time: " + (System.currentTimeMillis() - start) + " ms");
return true;
}CompletableFuture provides powerful asynchronous programming capabilities, supporting synchronous, asynchronous, and task‑orchestration patterns; its API resembles JavaScript's Promise.
All methods demonstrated.
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.
