Java Concurrency with Project Loom: Part 4 - Structured Concurrency in Practice
Discover how Structured Concurrency brings order to parallel programming. Master the StructuredTaskScope API to manage hierarchical tasks, ensure automatic cancellation, and prevent resource leaks in concurrent Java applications.
J
Jagdish Salgotra
staff eng · writes from production
2025-07-27·25 min read·~1,100 words
Companion · for this piece
Ask
Ask NoteSensei.
A reading assistant that only knows what's in this article. Sources every answer to a passage you can re-read.
Test
Test your understanding.
Five questions drawn from the piece. Earn a grade. See the passage behind anything you miss.
Note
This series uses Java 21 as the baseline. Structured concurrency snippets in this part (StructuredTaskScope, JEP 453) use preview APIs and require --enable-preview.
TL;DR
Structured concurrency keeps related concurrent tasks in one lifecycle boundary
Automatic cancellation means one failure can stop sibling tasks early
In one sample benchmark, StructuredTaskScope showed lower coordination overhead than CompletableFuture
The programming model stays close to try-with-resources
Scope-based cleanup reduces resource-leak risk in failure paths
The Async Programming Problem in Production
Long CompletableFuture chains look clean in reviews and painful in incident calls.
The common issues are predictable: hard-to-follow stack traces, cleanup gaps that appear only under load, and cancellation logic that behaves inconsistently between happy-path and failure-path traffic.
"Why is Service C still running when Service A failed?" is a common production question with ad-hoc async orchestration.
The CompletableFuture Reality Check
Here's what most of us end up writing when we need to call multiple services:
Resource leak risk: failed tasks may continue running and consuming resources
Complex exception paths: nested handling across async boundaries
Debugging overhead: stack traces and failure lineage are harder to follow
Manual cleanup burden: cancellation and timeout paths are easy to miss
Memory pressure: incomplete futures can retain resources longer than expected
Structured Concurrency Approach
Structured concurrency is based on a simple principle: related tasks should be managed together as a unit. When one fails, the group can fail fast and clean up automatically.
Let's see how the same dashboard service looks with structured concurrency:
Use this when redundant providers can satisfy the same request and only the first successful result matters.
Real-World Performance Analysis
Benchmark Setup
These benchmark numbers are from one simplified test environment. Real workloads vary widely based on downstream behavior, JVM configuration, and traffic shape.
Test Environment:
Java 21 with preview features enabled
16GB RAM, 8-core CPU
1000 iterations per test (after 100 warmup runs)
Virtual threads for both approaches
Test 1: Service Orchestration Performance
Scenario: User dashboard requiring data from 3 services:
User Service: 200ms response time
Order Service: 300ms response time
Profile Service: 100ms response time
Expected optimal time: ~300ms (limited by slowest service)
privatestatic String generatePrometheusMetrics() {
StringBuildermetrics=newStringBuilder();
MemoryUsageheapUsage= memoryBean.getHeapMemoryUsage();
metrics.append("# HELP jvm_memory_used_bytes Used memory in bytes\n");
metrics.append("# TYPE jvm_memory_used_bytes gauge\n");
metrics.append("jvm_memory_used_bytes{area=\"heap\"} ").append(heapUsage.getUsed()).append("\n");
metrics.append("# HELP jvm_memory_max_bytes Maximum memory in bytes\n");
metrics.append("# TYPE jvm_memory_max_bytes gauge\n");
metrics.append("jvm_memory_max_bytes{area=\"heap\"} ").append(heapUsage.getMax()).append("\n");
ManagementFactory.getGarbageCollectorMXBeans().forEach(gc -> {
metrics.append("# HELP jvm_gc_collection_seconds Time spent in GC\n");
metrics.append("# TYPE jvm_gc_collection_seconds counter\n");
metrics.append("jvm_gc_collection_seconds{gc=\"").append(gc.getName()).append("\"} ")
.append(gc.getCollectionTime() / 1000.0).append("\n");
});
return metrics.toString();
}
What's Next?
In Part 5, we'll explore advanced structured concurrency patterns for real-world scenarios: conditional cancellation, progressive result collection, and fault-tolerant orchestration.
We'll cover:
Timeout patterns that prevent cascading failures
Conditional cancellation for complex business workflows
Resource-aware scheduling and the bulkhead pattern
Building circuit breakers with structured concurrency