A Spring Boot 3.2 demo showcasing Java 21 Virtual Threads (Project Loom) and Structured Concurrency (JEP 453).
Parallel API aggregation with timeouts and cancellation — no reactive stack required.
Features • Quick Start • API • Architecture
| Feature | Description |
|---|---|
| Virtual threads | Tomcat and @Async run on virtual threads (spring.threads.virtual.enabled: true). One request = one lightweight thread; blocking I/O is cheap. |
| ExecutorService on VTs | Parallel calls to two “external” services (stubs) via Executors.newVirtualThreadPerTaskExecutor() with a 2s timeout and cancel-on-timeout. |
| StructuredTaskScope | Same scenario using JEP 453 structured concurrency: one scope, fork + joinUntil, automatic cancellation on failure or timeout. |
| AsyncConfigurer | Custom executor so all @Async methods run on virtual threads. |
| Docker one-liner | docker compose up --build to build and run; no local JDK required. |
| Tests | Unit tests for the service (both code paths); integration tests for the controller. |
flowchart LR
subgraph Client
C[HTTP Client]
end
subgraph "Spring Boot :8080"
R[Controller]
R --> E["/api/service\nExecutorService"]
R --> S["/api/service/structured\nStructuredTaskScope"]
E --> VT1[Virtual Thread 1]
E --> VT2[Virtual Thread 2]
S --> VT3[Virtual Thread 3]
S --> VT4[Virtual Thread 4]
end
VT1 --> A[callServiceA]
VT2 --> B[callServiceB]
VT3 --> A
VT4 --> B
C --> R
- ExecutorService path: Two tasks submitted to a virtual-thread executor;
Future.get(2, SECONDS)with cancel on timeout. - StructuredTaskScope path:
ShutdownOnFailurescope,forkfor each call,joinUntil(deadline), thenthrowIfFailed()and collect results.
- JDK 21 (for local run and tests)
- Docker and Docker Compose (for containerized run)
git clone https://github.com/NullPoint3rDev/virtual-threads.git
cd virtual-threads
docker compose up --build- API: http://localhost:8081/api/service and http://localhost:8081/api/service/structured
- Health: http://localhost:8081/actuator/health
Port 8081 is used by default to avoid clashes with other apps on 8080. Change docker-compose.yml to "8080:8080" if you prefer.
./gradlew bootRunThen open http://localhost:8080/api/service (and /api/service/structured).
StructuredTaskScope is a preview API in Java 21; the build enables --enable-preview for compile and run.
| Method | Path | Description |
|---|---|---|
| GET | /api/service |
Aggregates two stub calls using ExecutorService (virtual threads). |
| GET | /api/service/structured |
Same using StructuredTaskScope.ShutdownOnFailure. |
Example response:
{"serviceA":"result-A","serviceB":"result-B"}Health check: GET /actuator/health → {"status":"UP"}.
./gradlew test- VirtualThreadsServiceTest — unit tests for
fetchWithVirtualThreads()andfetchWithStructuredConcurrency()with a real virtual-thread executor. - VirtualThreadsControllerTest —
@SpringBootTest+MockMvcfor both endpoints.
Preview is enabled for the test JVM via jvmArgs("--enable-preview") in build.gradle.kts.
| Technology | Role |
|---|---|
| Java 21 | Virtual threads (JEP 444), StructuredTaskScope (JEP 453, preview). |
| Spring Boot 3.2 | Web, Actuator; native virtual-thread support. |
| Gradle 8.5 | Build; --enable-preview for main, test, and bootRun. |
| Docker | Multi-stage Dockerfile (Eclipse Temurin 21 JDK → JRE); --enable-preview in container entrypoint. |