Spring Boot API Aggregation: Sequential, CompletableFuture, and Virtual Thread Structured Concurrency Performance

The article benchmarks three Spring Boot aggregation approaches—sequential calls, CompletableFuture parallelism, and virtual‑thread structured concurrency—using JMeter on a Spring Boot 3.5.0 service, showing that CompletableFuture delivers the lowest latency, structured concurrency offers better reliability, and sequential execution performs the worst.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot API Aggregation: Sequential, CompletableFuture, and Virtual Thread Structured Concurrency Performance

Environment

Spring Boot 3.5.0

1. Sequential calls

private final UserRepository ur;

public Map<String, Object> complexQuerySequential() {
    String name = "费青汉";
    List<User> users = this.ur.findByName(name);
    Long count = this.ur.countUser();
    User user = this.ur.findById(3599999).get();
    return Map.of("list", users, "count", count, "user", user);
}

This method performs three blocking database queries one after another, resulting in a longer overall response time.

2. CompletableFuture parallelism

public Map<String, Object> complexQueryCompletableFuture() {
    String name = "费青汉";
    Map<String, Object> ret = new HashMap<>();
    CompletableFuture.allOf(
        CompletableFuture.supplyAsync(() -> this.ur.findByName(name))
            .thenAccept(val -> ret.put("list", val)),
        CompletableFuture.supplyAsync(() -> this.ur.countUser())
            .thenAccept(val -> ret.put("count", val)),
        CompletableFuture.supplyAsync(() -> this.ur.findById(3599999).get())
            .thenAccept(val -> ret.put("user", val))
    ).join();
    return ret;
}

All three queries are launched asynchronously with supplyAsync; thenAccept stores each result in a shared map, and allOf().join() waits for completion.

3. Virtual‑thread structured concurrency

public Map<String, Object> complexQueryVirtualThreadStructuredConcurrent() {
    String name = "费青汉";
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        var listFuture = scope.fork(() -> this.ur.findByName(name));
        Subtask<Long> countFuture = scope.fork(() -> this.ur.countUser());
        Subtask<Optional<User>> userFuture = scope.fork(() -> this.ur.findById(3599999));
        scope.join();
        scope.throwIfFailed();
        return Map.of(
            "list", listFuture.get(),
            "count", countFuture.get(),
            "user", userFuture.get()
        );
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

This version uses virtual threads and StructuredTaskScope to fork the three queries, joins them, and propagates any failure. Compared with CompletableFuture, it simplifies lifecycle management and reduces the risk of resource leaks while keeping the code readable.

Note: Replacing the virtual‑thread factory with Thread.ofPlatform().factory() creates unlimited platform threads, which can eventually cause OOM.

4. Test setup

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;
    public UserController(UserService userService) { this.userService = userService; }
    @GetMapping("/sequential")
    public ResponseEntity<?> complexQuerySequential() {
        return ResponseEntity.ok(this.userService.complexQuerySequential());
    }
    @GetMapping("/completablefuture")
    public ResponseEntity<?> complexQueryCompletableFuture() {
        return ResponseEntity.ok(this.userService.complexQueryCompletableFuture());
    }
    @GetMapping("/vt")
    public ResponseEntity<?> complexQueryVirtualThreadStructuredConcurrent() {
        return ResponseEntity.ok(this.userService.complexQueryVirtualThreadStructuredConcurrent());
    }
}

The three endpoints are load‑tested with JMeter.

JMeter test results
JMeter test results

5. Results

CompletableFuture delivers the best performance : lowest response time and highest throughput in the benchmark.

Structured concurrency offers higher reliability : slightly slower than CompletableFuture but simplifies task management and error handling.

Sequential calls have the lowest efficiency : suitable only for low‑traffic or non‑real‑time scenarios.

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.

performance testingCompletableFuturespring-bootJMeterVirtual ThreadsStructured Concurrency
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.