Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
# CLAUDE.md — Java Virtual Threads & Concurrency
## When to use virtual threads
- Java 21+. Virtual threads scale to millions of concurrent tasks for I/O-bound work.
- Use them when you'd previously have used a thread pool sized in the hundreds. They're cheaper than OS threads.
- **Don't** use them for CPU-bound work. They run on a fixed pool of carrier threads — bottleneck is the same.
- **Don't** use them inside `synchronized` blocks that span I/O. Pinning to a carrier thread negates the benefit. Use `ReentrantLock` instead.
## How to spawn
- One-off: `Thread.startVirtualThread(() -> work())`.
- Pool-style: `Executors.newVirtualThreadPerTaskExecutor()` — every task gets its own virtual thread, no reuse.
- Don't `setMaxThreads` — there's no useful upper bound.
## Structured concurrency
- `StructuredTaskScope` for fan-out/fan-in. Lifetime is tied to the enclosing scope:
```java
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var a = scope.fork(() -> fetchA());
var b = scope.fork(() -> fetchB());
scope.join().throwIfFailed();
return new Pair<>(a.get(), b.get());
}
```
- `ShutdownOnFailure` cancels siblings on first failure. `ShutdownOnSuccess` returns first success.
- Always call `scope.join()` — the try-with-resources only closes the scope, not the work.
## CompletableFuture
- Still useful for callback-style composition.
- Prefer `thenApply`, `thenCompose`, `thenCombine` over chained `whenComplete`.
- Always specify the executor (`*Async` variants take one). The common pool surprises you.
## Locks
- `ReentrantLock` for non-trivial locking. `synchronized` is fine for short critical sections.
- `ReadWriteLock` only when reads dominate writes by an order of magnitude. Otherwise it's slower than `Lock`.
- `StampedLock` for optimistic reads. Read the JavaDoc — it's not a drop-in replacement.
## Atomics
- `AtomicInteger`, `AtomicReference` for lock-free counters and references.
- `LongAdder` over `AtomicLong` when many threads write and few read. Better contention behavior.
- Use `volatile` only for write-once-publish or visibility-only flags. Compound operations need atomics.
## Cancellation
- Always honor `Thread.interrupted()` in long loops.
- `InterruptedException`: catch, restore the flag (`Thread.currentThread().interrupt()`), and bail out.
- Don't swallow interrupts. They're a cancellation signal from the caller.
## Don't
- Don't sleep in I/O code. If you need a timeout, use `CompletableFuture.orTimeout(...)`.
- Don't pin virtual threads inside `synchronized` blocks holding I/O calls.
- Don't share mutable collections across threads. Use `ConcurrentHashMap` or copy-on-write variants.
- Don't use `Thread.stop()`. Ever.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.
Spring Framework Core Rules
Dependency injection, bean lifecycle, configuration classes, and profiles.