The MicroProfile Fault Tolerance 4.1 specification defines six composable policies. This page lays out the vocabulary (Retry, Timeout, CircuitBreaker, Bulkhead, Fallback, Asynchronous), the canonical composition order mandated by §2.5, and the conceptual distinction between recovery and protection policies.
The six policies
| Annotation | Family | Role |
|---|---|---|
|
Recovery |
Transparent retries on transient failure — delay, jitter, |
|
Protection |
Hard upper bound on the execution time of an attempt. |
|
Protection |
Cuts calls to a failing service — sliding window, CLOSED/OPEN/HALF_OPEN transitions. |
|
Protection |
Isolates a call site — semaphore (sync) or bounded queue (async). |
|
Recovery |
Provides an alternative result when all other policies have failed. |
|
Execution |
Delegates the invocation to a virtual thread; the method returns |
Key distinction: recovery (@Retry, @Fallback) tries to obtain a result despite failure; protection (@Timeout, @CircuitBreaker, @Bulkhead) tries to bound the damage taken by the calling system.
Canonical composition order
MP FT 4.1 spec §2.5 mandates a single wrapping order, from the outside in:
@Asynchronous sits above this chain when present: it switches execution onto a virtual thread, then the @Fallback → … → method chain runs in that thread.
Concrete consequence: composition is non-commutative. A @Retry around a @Timeout (what the chain does) retries each timed attempt. Conversely, a @Timeout around a @Retry (which MP FT does not let you express) would time-limit the entire retry sequence. The spec picks the first form — each attempt has its own time budget.
Circuit breaker states
@CircuitBreaker is a three-state machine:
| State | Semantics |
|---|---|
|
Calls pass. The |
|
Calls are rejected immediately with |
|
One probe attempt is allowed. If |
Heisenberg metaphor: the OPEN state is the wave packet collapse — observation forced the system into a discrete state, and we must wait delay before superposition (the HALF_OPEN probe) becomes possible again.
@Fallback vs @CircuitBreaker
Common confusion: both intervene on failure, but their roles are orthogonal.
| Aspect | @Fallback |
@CircuitBreaker |
|---|---|---|
Family |
Recovery |
Protection |
Question asked |
"What do we return if the call failed?" |
"Should we even try to call?" |
Effect on caller |
Sees a result (fallback) instead of an exception |
Sees |
Effect on remote system |
None — the call already happened |
Reduces load — subsequent calls are short-circuited |
The two combine naturally: @CircuitBreaker protects, @Fallback recovers the CircuitBreakerOpenException thrown.
@Asynchronous semantics
@Asynchronous requires the method to return CompletionStage<T> or Future<T>. The call is delegated to a virtual thread. For CompletionStage:
-
The interceptor immediately returns an empty
CompletableFuture<T>. -
A virtual thread is created (
Thread.ofVirtual().start(…)). -
The
@Fallback → @CircuitBreaker → @Bulkhead → @Timeout → @Retry → methodchain runs on that virtual thread. -
When the result is known,
CompletableFuture.complete(value)is called.
For Future<T>, the contract is stricter: Future.cancel(true) interrupts the virtual thread (best-effort), Future.get(timeout) blocks the calling thread until timeout.
|
A method annotated |
@Bulkhead semantics
@Bulkhead(value=N) bounds the number of concurrent invocations:
-
Synchronous mode (no
@Asynchronous): aSemaphorewith N permits; a call beyond raisesBulkheadExceptionimmediately. -
Asynchronous mode (with
@Asynchronous): aSemaphorewith N permits + awaitingTaskQueuequeue of pending tasks; beyond that,BulkheadException.
Configuration through MicroProfile Config
MP FT 4.1 spec §12 defines a fault-tolerance/… key scheme read through MicroProfile Config — provided by Ravel.
| Key | Effect |
|---|---|
|
Global value, takes precedence over the annotation default. |
|
Class-level override. |
|
Method-level override — highest precedence. |
|
Boolean. |
|
Boolean. |
See Reference for the exhaustive list and per-parameter variants.