Virtual Thread Support#
Resilience4j added comprehensive Java 21 virtual thread (Project Loom) support in PR #2335. The feature is opt-in and fully backward-compatible β existing applications continue to use platform threads unless explicitly configured otherwise.
Configuration#
Thread type is resolved in priority order :
- JVM system property:
-Dresilience4j.thread.type=virtual - Environment variable:
RESILIENCE4J_THREAD_TYPE=virtual - Default:
platform
Spring Boot 3/4 auto-configuration exposes the same setting as a YAML property:
resilience4j:
thread:
type: virtual
Auto-configuration classes (Resilience4jThreadAutoConfiguration, ThreadMetricsAutoConfiguration) are registered in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
Core Abstractions#
ThreadType enum#
ThreadType (resilience4j-core) has two constants β VIRTUAL and PLATFORM β with PLATFORM as the default. Use ThreadType.fromString(value) (case-insensitive) or the safe ThreadType.fromStringOrDefault(value, default) to parse configuration strings.
ExecutorServiceFactory#
ExecutorServiceFactory is the central factory for all internal ScheduledExecutorService instances. Key methods:
getThreadType()β reads the system property / env var and returns the activeThreadType.newSingleThreadScheduledExecutor(poolName)β creates a single-thread scheduler, virtual or platform depending on configuration.newScheduledThreadPool(size, poolName)β creates a fixed-size pool.
Virtual threads are named {poolName}-vthread-0, {poolName}-vthread-1, etc.; platform threads use {poolName}-thread-N with daemon mode.
Consumers: FixedThreadPoolBulkhead, SchedulerFactory (circuit breaker), and SemaphoreBasedRateLimiter all delegate to this factory.
Registry Store: FutureHashMap Pattern#
The most critical virtual-thread-specific design is in InMemoryRegistryStore.
Problem: ConcurrentHashMap.computeIfAbsent() holds a segment lock while the mapping function runs. If the mapping function blocks (I/O, DB call, expensive initialization), virtual threads get pinned to their carrier thread β eliminating the scalability benefit of virtual threads.
Solution β FutureHashMap pattern :
The map stores CompletableFuture<E> values instead of E directly. computeIfAbsent uses ConcurrentHashMap.putIfAbsent (no lock held during execution) to race threads for a slot:
- Winner thread β inserts a new incomplete
CompletableFuture, runsmappingFunction.apply()outside any map lock, then callsfuture.complete(value). - Loser threads β call
future.join(), which parks viaLockSupport.park()β virtual-thread-friendly, no carrier thread blocking. - Failure handling β if the mapping function throws,
completeExceptionally()is called; the entry is removed only if the caller won thecompleteExceptionallyCAS, avoiding race conditions.
Benchmark results from the PR show this approach achieves 101.2 ops/s at 100 concurrent virtual threads vs. 25.9 ops/s with a plain ConcurrentHashMap (which caused thread pinning).
β οΈ Do not simplify
computeIfAbsentback to the standardConcurrentHashMapversion β the comment in the source explicitly flags this as intentional and notes that a future JDK simplification may be possible only when JDK 25 optimizescomputeIfAbsentinternally.
All other InMemoryRegistryStore operations (putIfAbsent, remove, replace, values) also operate on CompletableFuture<E> entries and use future.join() for retrieval, maintaining consistent virtual-thread-friendly behavior.
Concurrency correctness is tested in InMemoryRegistryStoreTest, which includes scenarios for concurrent computeIfAbsent + putIfAbsent, remove, replace, and failure injection via a slow/failing mapping function.
Module-Level Changes#
| Module | Change |
|---|---|
| Bulkhead | FixedThreadPoolBulkhead creates thread pools via ExecutorServiceFactory |
| CircuitBreaker | SchedulerFactory auto-detects thread type |
| RateLimiter | SemaphoreBasedRateLimiter updated for virtual thread compatibility |
| Retry / TimeLimiter | Parameterized tests now run on both thread types |
| Micrometer | ThreadMetrics added for monitoring virtual thread pools |
Key Source Files#
| File | Purpose |
|---|---|
ThreadType.java | Enum: VIRTUAL / PLATFORM with parse helpers |
ExecutorServiceFactory.java | Central factory for all internal executors |
InMemoryRegistryStore.java | FutureHashMap pattern β virtual-thread-safe registry store |
InMemoryRegistryStoreTest.java | Concurrency correctness tests for registry store |