Retry Mechanism#
The Resilience4j retry module provides fault-tolerance through configurable retry logic with pluggable backoff strategies. The core entry point is RetryImpl, which implements Retry and manages two execution contexts: ContextImpl (synchronous) and AsyncContextImpl (async/CompletableFuture). Configuration is declared via RetryConfig and passed at construction time.
Execution Flow#
Both ContextImpl and AsyncContextImpl follow the same decision tree on each call:
onError/onRuntimeErrorβ check whether the exception matchesexceptionPredicate. Non-matching exceptions are immediately re-thrown and counted asfailedWithoutRetry; matching exceptions proceed to sleep-or-throw.throwOrSleepAfterExceptionβ incrementsnumOfAttempts. IfnumOfAttempts >= maxAttempts, publishes aRetryOnErrorEventand re-throws the original exception . Otherwise, callswaitIntervalAfterException.waitIntervalAfterExceptionβ callsintervalBiFunction.apply(attempt, Either.left(throwable))to compute the sleep interval, then callssleepFunction.accept(interval).onResultβ if aresultPredicateis configured and matches the result, the call is treated as a retry trigger .onCompleteβ updates success/failure counters and, whenfailAfterMaxAttemptsis true, throwsMaxRetriesExceededException.
For async contexts, AsyncContextImpl.onError additionally unwraps CompletionException / ExecutionException before matching . Instead of sleeping directly, it returns the computed interval as a long for the caller to schedule .
Backoff Interval: IntervalFunction vs. IntervalBiFunction#
Two functional interfaces compute wait intervals:
| Interface | Signature | Context |
|---|---|---|
IntervalFunction | Integer β Long | Attempt count only |
IntervalBiFunction | (Integer, Either<Throwable, T>) β Long | Attempt count + last exception or result |
IntervalBiFunction is the internal contract RetryImpl always uses . When only an IntervalFunction is configured, RetryConfig.Builder.build() wraps it automatically via IntervalBiFunction.ofIntervalFunction() , discarding the Either parameter at call time. This lets interval functions optionally react differently when a retry was triggered by a result predicate (right side) versus an exception (left side).
Built-in strategies available on IntervalFunction :
| Factory method | Behavior |
|---|---|
ofDefaults() | Fixed 500 ms |
of(intervalMillis) | Fixed custom interval |
ofRandomized(...) | Fixed interval Β± randomization factor |
ofExponentialBackoff(...) | Exponential growth, default multiplier 1.5Γ |
ofExponentialRandomBackoff(...) | Exponential + jitter, optional max cap |
Default constants: DEFAULT_INITIAL_INTERVAL = 500 ms, DEFAULT_MULTIPLIER = 1.5, DEFAULT_RANDOMIZATION_FACTOR = 0.5 .
Validation Strategy: Eager (Build-Time) vs. Deferred (Runtime)#
Eager (build-time): IntervalFunction factory methods validate parameters immediately at construction β before any retry runs. checkInterval rejects values < 1 ms, checkRandomizationFactor rejects values outside [0.0, 1.0] . Additionally, RetryConfig.Builder.build() enforces that intervalFunction and intervalBiFunction are not set simultaneously, throwing IllegalStateException if both are configured . After build(), intervalBiFunction is always non-null .
Deferred (runtime): The computed interval value is implicitly validated at runtime. A negative interval returned by intervalBiFunction is treated as a signal to publish a terminal RetryOnErrorEvent and skip the sleep entirely, without throwing an additional exception . The actual constraint on the computed value is only exercised by sleepFunction.accept(interval).
Note: The retry-module-specific
IntervalFunctionapplies a stricter minimum of 10 ms (vs. the core library's 1 ms), so module-level policies may differ.
Exception Propagation#
| Situation | What is thrown |
|---|---|
Exception not matched by exceptionPredicate | Original exception, immediately |
| Max attempts reached (checked exception) | Original last exception, re-thrown from throwOrSleepAfterException |
| Max attempts reached (runtime exception) | Original last RuntimeException, re-thrown |
failAfterMaxAttempts = true + result predicate exhausted | MaxRetriesExceededException |
| Sleep interrupted (checked exception path) | Thread interrupted, then re-throws original lastException |
| Sleep interrupted (runtime exception path) | Re-throws lastRuntimeException, or wraps in CancellationException if null |
MaxRetriesExceededException is a RuntimeException created via a static factory createMaxRetriesExceededException(Retry) that includes the retry name and max attempts in the message .
Key Source Files#
| File | Purpose |
|---|---|
RetryImpl.java | Core sync/async execution contexts, sleep logic, event publishing |
RetryConfig.java | Configuration builder with eager validation |
IntervalFunction.java | Built-in backoff strategies and build-time parameter validation |
IntervalBiFunction.java | Functional interface for attempt+result-aware interval computation |
MaxRetriesExceededException.java | Exception thrown when failAfterMaxAttempts is enabled |