Java concurrency
Purpose
Translate requirements and evidence into the concurrency model with the clearest ownership,
lifetime, cancellation, bounds, and failure semantics. Syntax does not create capacity: every
in-flight operation consumes CPU, memory, connections, queue slots, downstream capacity, or
recovery budget.
This category stops at one JVM. Cross-process retries, leases, ordering, and partial failure belong
to distributed-system skills.
Classification contract
Start with the actual compiler release/toolchain, resolved framework versions, runtime image
and preview policy. This router uses Java 21–25 as its reference range; the target project may
be older. Neither adopting a construct nor reading a Java 25 source authorizes a JDK upgrade,
new dependency or preview flag. Mark missing measurements as unknown and keep performance
recommendations conditional rather than inventing a thread count.
unit of work and semantic result:
arrival shape: request/value/stream/scheduled/background
lifetime owner: lexical request/service/application
CPU demand, blocking/wait mechanisms and variability:
shared state, ordering and consistency requirements:
scarce resources and capacity per resource:
concurrency/admission/queue bounds and overload policy:
deadline, cancellation propagation and cleanup:
failure aggregation, retry/partial result policy:
context propagation and observability identity:
JDK/API status and framework constraints:
Route by dominant decision
| Decision |
Owning skill |
| executor ownership, queue, rejection, shutdown |
executors-and-task-lifecycle |
| platform-thread sizing and virtual-thread concurrency |
thread-sizing-and-virtual-threads |
| virtual-thread scheduler, mounting/pinning |
virtual-threads-internals |
| migrating an existing service |
virtual-thread-migration |
| lexical fan-out/join/cancel/failure |
structured-concurrency |
| immutable dynamic context |
scoped-values |
| callback/stage graph |
completablefuture-composition |
| CPU-decomposable work/work stealing |
forkjoinpool-and-work-stealing |
| demand-controlled stream |
reactive-backpressure |
| reactive versus thread-per-task |
reactive-and-virtual-thread-selection |
| cancellation/interruption/cleanup |
cancellation-and-interruption |
| admission/concurrency isolation |
concurrency-limiting-and-bulkheads |
| JMM/publication/visibility/atomicity |
java-memory-model |
| collections/synchronizers |
concurrent-collections-and-synchronizers |
| CAS/nonblocking algorithm |
lock-free-patterns |
| deadlock/starvation/contention/dumps |
concurrency-diagnostics |
| correctness/stress/model tests |
concurrency-testing |
| quantitative queue/capacity model |
littles-law-and-queueing |
Selection principles
- Concurrency and parallelism are independent quantities. Concurrency is overlapping work;
parallelism is simultaneous execution. CPU throughput can also be limited by quota, memory
bandwidth, locks, cache coherence, I/O completion, vectorization, or another device—not only a
physical core count.
- Classify phases, not an entire service. One request can parse on CPU, block on a socket,
contend on a pool, then execute CPU work. Choose boundaries and limits per phase.
- Cheap waiting does not create downstream capacity. Virtual threads can make a synchronous
blocking style scalable when blockers cooperate, but connections, memory, APIs and quotas remain
bounded.
- Lexical lifetime favors structured ownership, when a supported API/framework fits. Long-lived
consumers, schedulers and supervisors need an explicit service lifecycle.
- A future is a value handle; reactive streams are a demand protocol. Do not select either only
to avoid blocking syntax.
- State ownership precedes primitive choice. Prefer immutable snapshots/confinement when they
match semantics; otherwise define atomic invariants and happens-before before locks/atomics.
- Overload behavior is part of correctness. Bound, reject, queue, shed, degrade, or backpressure
deliberately; an unbounded queue transfers the bound to latency and heap.
A semaphore or connection pool bounds active resource users, not the number of tasks waiting
to acquire it. Also bound admission/waiters, acquisition time and retained request state.
- Cancellation is cooperative. Define propagation, interrupt behavior, noninterruptible calls,
resource cleanup, partial side effects, and what happens after the caller leaves.
Evidence before “more threads”
Measure aligned:
arrival/completion/error/drop rate and in-flight work
queue depth/wait/service time per scarce resource
CPU by cgroup/process/thread and throttling
wall/off-CPU state and dependency/pool/lock duration
allocation/retained memory per in-flight operation
deadline/cancellation outcome and abandoned work
Low CPU plus high latency does not prove a queue, nor that adding concurrency helps. The system may
be idle because of admission, timers, external wait, serial dependency, lost work, measurement scope,
or traffic changes. Locate the wait and its owner.
Decision tree
one request/task with sequential blocking calls?
-> synchronous style; consider virtual threads if concurrency and blocker support justify it
lexical fan-out whose subtasks must join/cancel together?
-> structured concurrency if target API status is acceptable; otherwise explicit task-group ownership
callback-only or dependency graph of values?
-> CompletableFuture/stage abstraction with explicit executor and cancellation bridge
unbounded/time-shaped stream with consumer demand?
-> Reactive Streams or bounded queue/channel with protocol-level backpressure
CPU-decomposable finite computation?
-> bounded parallel decomposition/ForkJoin after granularity and interference analysis
long-lived scheduled/consumer work?
-> managed executor/supervisor with shutdown, retry and health semantics
These branches can coexist at explicit boundaries. Do not hide blocking work inside an event loop or
CPU pool; do not wrap synchronous work in futures without defining the execution resource.
Version discipline
Virtual threads are final in Java 21; later JDKs change implementation details such as monitor
pinning. Scoped values and structured concurrency have evolved through preview/incubator stages.
In Java 25, ScopedValue is final while StructuredTaskScope is still preview; do not infer
one API's status from the other. Scoped bindings do not make a mutable bound object immutable,
and arbitrary executor submissions do not automatically inherit them.
Before emitting source, verify the exact target JDK's JEP/API status, preview flags, binary/source
compatibility, and framework/tooling support. Do not encode a moving API from memory.
Deliverable
Return the dominant decision and owning specialist skill, the selected model with its task owner
and resource bound, one relevant rejected alternative, and the evidence/test that would confirm
the choice. For an incident, separate observed waits from the suspected cause. Unknown workload
or cancellation behavior is an explicit gap, not a capacity estimate. Keep this proportional;
do not expand a routing answer into implementation of every listed construct.
Review checklist
Anti-patterns
| Anti-pattern |
Failure |
Better approach |
Narrow exception |
| “I/O-bound => virtual threads” |
blocker/protocol/resource bounds ignored |
classify calls, target support, concurrency budget |
controlled blocking service |
| Bigger pool for latency |
queues/saturation amplify |
locate wait and size/admit from capacity |
measured underutilized local executor |
| Async means faster |
scheduling/context/error cost added |
choose for ownership/composition semantics |
callback adaptation |
| Reactive because blocking is obsolete |
complexity without stream demand need |
virtual-thread/synchronous style or bounded queue |
genuine demand-driven pipeline |
| Pool as downstream limiter |
limiter disappears on migration |
explicit resource-local admission control |
executor is the resource itself |
| Fire-and-forget |
orphan failure/work/leak |
owned lifecycle and result policy |
bounded best-effort telemetry |
References
1---2name: java-concurrency3description: Entry point for designing or triaging concurrency inside one JVM. Classifies work by lifecycle, blocking and CPU demand, state ownership, arrival shape, ordering, cancellation, failure, and scarce-resource bounds, then routes to executors, virtual threads, structured concurrency, futures, reactive streams, memory-model correctness, diagnostics, or testing. Use before selecting a concurrency abstraction or when “more threads,” “async,” or “reactive” is proposed as a performance fix. Detailed construct internals and distributed coordination have separate owners.4---56# Java concurrency78## Purpose910Translate requirements and evidence into the concurrency model with the clearest ownership,11lifetime, cancellation, bounds, and failure semantics. Syntax does not create capacity: every12in-flight operation consumes CPU, memory, connections, queue slots, downstream capacity, or13recovery budget.1415This category stops at one JVM. Cross-process retries, leases, ordering, and partial failure belong16to distributed-system skills.1718## Classification contract1920Start with the actual compiler release/toolchain, resolved framework versions, runtime image21and preview policy. This router uses Java 21–25 as its reference range; the target project may22be older. Neither adopting a construct nor reading a Java 25 source authorizes a JDK upgrade,23new dependency or preview flag. Mark missing measurements as unknown and keep performance24recommendations conditional rather than inventing a thread count.2526```text27unit of work and semantic result:28arrival shape: request/value/stream/scheduled/background29lifetime owner: lexical request/service/application30CPU demand, blocking/wait mechanisms and variability:31shared state, ordering and consistency requirements:32scarce resources and capacity per resource:33concurrency/admission/queue bounds and overload policy:34deadline, cancellation propagation and cleanup:35failure aggregation, retry/partial result policy:36context propagation and observability identity:37JDK/API status and framework constraints:38```3940## Route by dominant decision4142| Decision | Owning skill |43| ----------------------------------------------------- | ------------------------------------------ |44| executor ownership, queue, rejection, shutdown | `executors-and-task-lifecycle` |45| platform-thread sizing and virtual-thread concurrency | `thread-sizing-and-virtual-threads` |46| virtual-thread scheduler, mounting/pinning | `virtual-threads-internals` |47| migrating an existing service | `virtual-thread-migration` |48| lexical fan-out/join/cancel/failure | `structured-concurrency` |49| immutable dynamic context | `scoped-values` |50| callback/stage graph | `completablefuture-composition` |51| CPU-decomposable work/work stealing | `forkjoinpool-and-work-stealing` |52| demand-controlled stream | `reactive-backpressure` |53| reactive versus thread-per-task | `reactive-and-virtual-thread-selection` |54| cancellation/interruption/cleanup | `cancellation-and-interruption` |55| admission/concurrency isolation | `concurrency-limiting-and-bulkheads` |56| JMM/publication/visibility/atomicity | `java-memory-model` |57| collections/synchronizers | `concurrent-collections-and-synchronizers` |58| CAS/nonblocking algorithm | `lock-free-patterns` |59| deadlock/starvation/contention/dumps | `concurrency-diagnostics` |60| correctness/stress/model tests | `concurrency-testing` |61| quantitative queue/capacity model | `littles-law-and-queueing` |6263## Selection principles6465- **Concurrency and parallelism are independent quantities.** Concurrency is overlapping work;66 parallelism is simultaneous execution. CPU throughput can also be limited by quota, memory67 bandwidth, locks, cache coherence, I/O completion, vectorization, or another device—not only a68 physical core count.69- **Classify phases, not an entire service.** One request can parse on CPU, block on a socket,70 contend on a pool, then execute CPU work. Choose boundaries and limits per phase.71- **Cheap waiting does not create downstream capacity.** Virtual threads can make a synchronous72 blocking style scalable when blockers cooperate, but connections, memory, APIs and quotas remain73 bounded.74- **Lexical lifetime favors structured ownership**, when a supported API/framework fits. Long-lived75 consumers, schedulers and supervisors need an explicit service lifecycle.76- **A future is a value handle; reactive streams are a demand protocol.** Do not select either only77 to avoid blocking syntax.78- **State ownership precedes primitive choice.** Prefer immutable snapshots/confinement when they79 match semantics; otherwise define atomic invariants and happens-before before locks/atomics.80- **Overload behavior is part of correctness.** Bound, reject, queue, shed, degrade, or backpressure81 deliberately; an unbounded queue transfers the bound to latency and heap.82 A semaphore or connection pool bounds active resource users, not the number of tasks waiting83 to acquire it. Also bound admission/waiters, acquisition time and retained request state.84- **Cancellation is cooperative.** Define propagation, interrupt behavior, noninterruptible calls,85 resource cleanup, partial side effects, and what happens after the caller leaves.8687## Evidence before “more threads”8889Measure aligned:9091```text92arrival/completion/error/drop rate and in-flight work93queue depth/wait/service time per scarce resource94CPU by cgroup/process/thread and throttling95wall/off-CPU state and dependency/pool/lock duration96allocation/retained memory per in-flight operation97deadline/cancellation outcome and abandoned work98```99100Low CPU plus high latency does not prove a queue, nor that adding concurrency helps. The system may101be idle because of admission, timers, external wait, serial dependency, lost work, measurement scope,102or traffic changes. Locate the wait and its owner.103104## Decision tree105106```text107one request/task with sequential blocking calls?108 -> synchronous style; consider virtual threads if concurrency and blocker support justify it109lexical fan-out whose subtasks must join/cancel together?110 -> structured concurrency if target API status is acceptable; otherwise explicit task-group ownership111callback-only or dependency graph of values?112 -> CompletableFuture/stage abstraction with explicit executor and cancellation bridge113unbounded/time-shaped stream with consumer demand?114 -> Reactive Streams or bounded queue/channel with protocol-level backpressure115CPU-decomposable finite computation?116 -> bounded parallel decomposition/ForkJoin after granularity and interference analysis117long-lived scheduled/consumer work?118 -> managed executor/supervisor with shutdown, retry and health semantics119```120121These branches can coexist at explicit boundaries. Do not hide blocking work inside an event loop or122CPU pool; do not wrap synchronous work in futures without defining the execution resource.123124## Version discipline125126Virtual threads are final in Java 21; later JDKs change implementation details such as monitor127pinning. Scoped values and structured concurrency have evolved through preview/incubator stages.128In Java 25, `ScopedValue` is final while `StructuredTaskScope` is still preview; do not infer129one API's status from the other. Scoped bindings do not make a mutable bound object immutable,130and arbitrary executor submissions do not automatically inherit them.131Before emitting source, verify the exact target JDK's JEP/API status, preview flags, binary/source132compatibility, and framework/tooling support. Do not encode a moving API from memory.133134## Deliverable135136Return the dominant decision and owning specialist skill, the selected model with its task owner137and resource bound, one relevant rejected alternative, and the evidence/test that would confirm138the choice. For an incident, separate observed waits from the suspected cause. Unknown workload139or cancellation behavior is an explicit gap, not a capacity estimate. Keep this proportional;140do not expand a routing answer into implementation of every listed construct.141142## Review checklist143144- [ ] Every task has an owner, terminal state, deadline/cancel path, and cleanup.145- [ ] Every executor/scope/subscription has bounded admission and shutdown behavior.146- [ ] Scarce-resource bounds sit at the resource and are tested under saturation.147- [ ] Context cannot leak tenant/security state across reused threads/tasks.148- [ ] Blocking calls are known and do not occupy forbidden event-loop/CPU workers.149- [ ] Shared state has a stated JMM/thread-safety contract and compound invariants.150- [ ] Errors, partial success, retries and cancellation races preserve business semantics.151- [ ] Queue/in-flight/active/completion/rejection/cancellation metrics use bounded cardinality.152- [ ] Load, failure, shutdown and concurrency correctness tests cover the chosen model.153154## Anti-patterns155156| Anti-pattern | Failure | Better approach | Narrow exception |157| ------------------------------------- | ---------------------------------------- | -------------------------------------------------- | ------------------------------------- |158| “I/O-bound => virtual threads” | blocker/protocol/resource bounds ignored | classify calls, target support, concurrency budget | controlled blocking service |159| Bigger pool for latency | queues/saturation amplify | locate wait and size/admit from capacity | measured underutilized local executor |160| Async means faster | scheduling/context/error cost added | choose for ownership/composition semantics | callback adaptation |161| Reactive because blocking is obsolete | complexity without stream demand need | virtual-thread/synchronous style or bounded queue | genuine demand-driven pipeline |162| Pool as downstream limiter | limiter disappears on migration | explicit resource-local admission control | executor is the resource itself |163| Fire-and-forget | orphan failure/work/leak | owned lifecycle and result policy | bounded best-effort telemetry |164165## References166167- [Choosing a construct](references/choosing-a-construct.md) — read when two models fit or168 when crossing executor, future, stream or task-group boundaries.169- [Concurrency versus parallelism](references/concurrency-vs-parallelism.md) — read when a170 throughput/latency claim or proposed concurrency increase needs a measurement plan.171- [Java 25 ScopedValue](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ScopedValue.html)172- [Java 25 StructuredTaskScope](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html)173- [Java concurrency API](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/package-summary.html)174- [JLS 17: Threads and locks](https://docs.oracle.com/javase/specs/jls/se25/html/jls-17.html)175- [JEP 444: Virtual Threads](https://openjdk.org/jeps/444)176- [JEP index](https://openjdk.org/jeps/0)