Retry
For introduction, see How to Retry on Failures.
Description
If a guarded method throws an exception, retry will swallow that exception and call the method again.
More specifically:
-
If the guarded method returns normally, its return value is returned.
-
Otherwise, if the guarded method throws an exception whose type is assignable to any of the
abortOn
types (see abortOn), retrying is aborted and the exception is rethrown. -
Otherwise, if the guarded method throws an exception whose type is assignable to any of the
retryOn
types (see retryOn), the method call is retried. -
Otherwise, the exception is rethrown.
Interactions with Other Strategies
See How to Use Multiple Strategies for an overview of how fault tolerance strategies are nested.
If @Fallback
is used with @Retry
, the fallback method or handler may be invoked if all retry attempts failed, depending on the fallback configuration.
Configuration
There are 9 configuration options, corresponding to the 9 members of the @Retry
annotation.
maxRetries
Type: int
Default: 3
The maximum number of retries.
-1
means the maximum number of retries is unlimited.
maxDuration
+ durationUnit
Type: long
+ ChronoUnit
Default: 180_000 millis
, or 3 minutes
The maximum duration for which retries will be attempted.
Note the naming inconsistency between maxDuration and durationUnit .
It is common for the time unit property to be named after the time value property, e.g. maxDurationUnit .
Fixing this inconsistency would be a breaking change the MicroProfile Fault Tolerance specification is not willing to make.
|
jitter
+ jitterDelayUnit
Type: long
+ ChronoUnit
Default: 200 millis
The maximum jitter to adjust the delay between retries. If set, the delay before each retry attempt will be computed as follows:
-
Generate random number from the closed interval
[-jitter, jitter]
. -
Add the generated random number to the configured
delay
. -
If the result is by any chance negative, use
0
.
Note the naming inconsistency between jitter and jitterDelayUnit .
It is common for the time unit property to be named after the time value property, e.g. jitterUnit .
Fixing this inconsistency would be a breaking change the MicroProfile Fault Tolerance specification is not willing to make.
|
Metrics
Retry exposes the following metrics:
Name |
|
Type |
|
Unit |
None |
Description |
The number of times the retry logic was run. This will always be once per method call. |
Tags |
|
Name |
|
Type |
|
Unit |
None |
Description |
The number of times the method was retried |
Tags |
|
See the Metrics reference guide for general metrics information.
Extra Features
Backoff Strategies for @Retry
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
When retrying failed operations, it is often useful to make a delay between retry attempts.
This delay is also called "backoff".
The @Retry
annotation in MicroProfile Fault Tolerance supports a single backoff strategy: constant.
That is, the delay between all retry attempts is identical (except for a random jitter).
SmallRye Fault Tolerance offers 3 annotations to specify a different backoff strategy:
-
@ExponentialBackoff
-
@FibonacciBackoff
-
@CustomBackoff
One of these annotations may be present on any program element (method or class) that also has the @Retry
annotation.
For example:
package com.example;
@ApplicationScoped
public class MyService {
@Retry
@ExponentialBackoff
public void hello() {
...
}
}
It is an error to add a backoff annotation to a program element that doesn’t have @Retry
(e.g. add @Retry
on a class and @ExponentialBackoff
on a method).
It is also an error to add more than one of these annotations to the same program element.
When any one of these annotations is present, it modifies the behavior specified by the @Retry
annotation.
The new behavior is as follows:
For @ExponentialBackoff
, the delays between retry attempts grow exponentially, using a defined factor
.
By default, the factor
is 2, so each delay is 2 * the previous delay.
For example, if the initial delay (specified by @Retry
) is 1 second, then the second delay is 2 seconds, third delay is 4 seconds, fourth delay is 8 seconds etc.
It is possible to define a maxDelay
, so that this growth has a limit.
For @FibonacciBackoff
, the delays between retry attempts grow per the Fibonacci sequence.
For example, if the initial delay (specified by @Retry
) is 1 second, then the second delay is 2 seconds, third delay is 3 seconds, fourth delay is 5 seconds etc.
It is possible to define a maxDelay
, so that this growth has a limit.
Both @ExponentialBackoff
and @FibonacciBackoff
also apply jitter, exactly like plain @Retry
.
Also, since @Retry
has a default maxDuration
of 3 minutes and default maxRetries
of 3, both @ExponentialBackoff
and @FibonacciBackoff
define a maxDelay
of 1 minute.
If we redefine maxRetries
to a much higher value, and the guarded method keeps failing, the delay would eventually become higher than 1 minute.
In that case, it will be limited to 1 minute.
Of course, maxDelay
can be configured.
If set to 0
, there’s no limit, and the delays will grow without bounds.
For @CustomBackoff
, computing the delays between retry attempts is delegated to a specified implementation of CustomBackoffStrategy
.
This is an advanced option.
For more information about these backoff strategies, see the javadoc of the annotations.
Predicate-Based @Retry
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
By default, an operation is retried only if it fails and the exception is assignable to one of the classes defined in @Retry.retryOn
(and not assignable to any of the classes defined in @Retry.abortOn
).
With the @RetryWhen
annotation, it is possible to instead define a custom predicate for the exception, as well as a predicate for the result.
The @RetryWhen
annotation may be present on any program element (method or class) that also has the @Retry
annotation.
For example:
package com.example;
@ApplicationScoped
public class MyService {
@Retry
@RetryWhen(
result = IsNull.class, (1)
exception = IsRuntimeException.class (2)
)
public String hello() {
...
}
public static final class IsNull implements Predicate<Object> {
@Override
public boolean test(Object o) {
return o == null;
}
}
public static final class IsRuntimeException implements Predicate<Throwable> {
@Override
public boolean test(Throwable throwable) {
return throwable instanceof RuntimeException;
}
}
}
1 | When the method returns null , it will be retried. |
2 | When the method throws a RuntimeException , it will be retried. |
All other results are considered expected and are not retried.
It is an error to specify @RetryWhen.exception
if @Retry.retryOn
/ @Retry.abortOn
is specified.
Specifying @RetryWhen.result
together with @Retry.retryOn
/ @Retry.abortOn
is possible, although perhaps not the best idea.
It is an error to add a @RetryWhen
annotation to a program element that doesn’t have @Retry
(e.g. add @Retry
on a class and @RetryWhen
on a method).
For more information about @RetryWhen
, see the javadoc of the annotation.
Before Retry Actions
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
The @BeforeRetry
annotation supports specifying an action that should be performed before any retry.
The action will not be performed before the initial attempt.
The @BeforeRetry
annotation may be present on any program element (method or class) that also has the @Retry
annotation.
For example:
package com.example;
@ApplicationScoped
public class MyService {
@Retry
@BeforeRetry(methodName = "beforeRetry") (1)
public String hello() {
...
}
public void beforeRetry() { (2)
...
}
}
1 | Specifies that before retrying, the beforeRetry method should be called. |
2 | The method must return void and have no parameters. |
Alternatively, it is possible to specify a class that is an implementation of the BeforeRetryHandler
interface:
package com.example;
@ApplicationScoped
public class MyService {
@Retry
@BeforeRetry(MyBeforeRetry.class) (1)
public String hello() {
...
}
public static class MyBeforeRetry implements BeforeRetryHandler {
public void handle(ExecutionContext context) {
...
}
}
}
1 | Specifies that the MyBeforeRetry handler should be called before retrying. |
It is only possible to specify one option; specifying both value
and methodName
leads to a deployment problem.
Note that the before retry action must fast and non-blocking (i.e., it must not do any IO or long-running computations) and must not throw an exception.
Inspecting Exception Cause Chains
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
This feature only works in the non-compatible mode. |
The @Retry
annotation can specify that certain exceptions should be treated as failures (retryOn
) and others as successes (abortOn
).
The specification limits this to inspecting the actual exception that was thrown.
However, in many cases, exceptions are wrapped and the exception the user wants to decide on is only present in the cause chain.
For that reason, in the non-compatible mode, if the actual thrown exception isn’t known failure or known success, SmallRye Fault Tolerance inspects the cause chain.
To be specific, in case a @Retry
method throws an exception, the decision process is:
-
If the
abortOn
exceptions are not default and the exception is assignable to one of theabortOn
exceptions, retry is aborted and the exception is rethrown. -
Otherwise, if the
retryOn
exceptions are not default and the exception is assignable to one of theretryOn
exceptions, retry is attempted. -
Otherwise, if the exception is assignable to one of the
abortOn
exceptions or its cause chain contains an exception assignable to one of theabortOn
exceptions, retry is aborted and the exception is rethrown. -
Otherwise, if the exception is assignable to one of the
retryOn
exceptions or its cause chain contains an exception assignable to one of theretryOn
exceptions, retry is attempted. -
Otherwise, the exception is rethrown.
For example:
@Retry(maxRetries = 5,
abortOn = ExpectedOutcomeException.class, (1)
retryOn = IOException.class) (2)
public Result doSomething() {
...
}
1 | If doSomething throws an ExpectedOutcomeException , or a WrapperException whose cause is ExpectedOutcomeException , retry is aborted and the exception is thrown. |
2 | If doSomething throws an IOException , or a WrapperException whose cause is IOException , retry is attempted. |
@Retry(maxRetries = 5,
abortOn = ExpectedOutcomeException.class) (1) (2)
public Result doSomething() {
...
}
1 | If doSomething throws an ExpectedOutcomeException , or a WrapperException whose cause is ExpectedOutcomeException , retry is aborted and the exception is thrown. |
2 | There’s no retryOn , so the 2nd step in the algorithm above is skipped.
This is what turns the WrapperException whose cause is ExpectedOutcomeException into an aborted retry. |