Document programmatic retry support in the reference manual

Closes gh-35436
This commit is contained in:
Sam Brannen 2025-09-07 12:20:27 +02:00
parent 7484b9c491
commit bce44b007d
1 changed files with 148 additions and 33 deletions

View File

@ -1,16 +1,19 @@
[[resilience]] [[resilience]]
= Resilience Features = Resilience Features
As of 7.0, the core Spring Framework includes a couple of common resilience features, As of 7.0, the core Spring Framework includes common resilience features, in particular
in particular `@Retryable` and `@ConcurrencyLimit` annotations for method invocations. <<resilience-annotations-retryable>> and <<resilience-annotations-concurrencylimit>>
annotations for method invocations as well as <<resilience-programmatic-retry,
programmatic retry support>>.
[[resilience-retryable]] [[resilience-annotations-retryable]]
== Using `@Retryable` == `@Retryable`
`@Retryable` is a common annotation that specifies retry characteristics for an individual {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`] is an annotation
method (with the annotation declared at the method level), or for all proxy-invoked that specifies retry characteristics for an individual method (with the annotation
methods in a given class hierarchy (with the annotation declared at the type level). declared at the method level), or for all proxy-invoked methods in a given class hierarchy
(with the annotation declared at the type level).
[source,java,indent=0,subs="verbatim,quotes"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@ -20,8 +23,8 @@ public void sendNotification() {
} }
---- ----
By default, the method invocation will be retried for any exception thrown: with at By default, the method invocation will be retried for any exception thrown: with at most 3
most 3 retry attempts after an initial failure, and a delay of 1 second between attempts. retry attempts after an initial failure, and a delay of 1 second between attempts.
This can be specifically adapted for every method if necessary for example, by narrowing This can be specifically adapted for every method if necessary for example, by narrowing
the exceptions to retry: the exceptions to retry:
@ -38,38 +41,45 @@ Or for 5 retry attempts and an exponential back-off strategy with a bit of jitte
[source,java,indent=0,subs="verbatim,quotes"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000) @Retryable(
includes = MessageDeliveryException.class,
maxAttempts = 5,
delay = 100,
jitter = 10,
multiplier = 2,
maxDelay = 1000)
public void sendNotification() { public void sendNotification() {
this.jmsClient.destination("notifications").send(...); this.jmsClient.destination("notifications").send(...);
} }
---- ----
Last but not least, `@Retryable` also works for reactive methods with a reactive Last but not least, `@Retryable` also works for reactive methods with a reactive return
return type, decorating the pipeline with Reactor's retry capabilities: type, decorating the pipeline with Reactor's retry capabilities:
[source,java,indent=0,subs="verbatim,quotes"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000) @Retryable(maxAttempts = 5, delay = 100)
public Mono<Void> sendNotification() { public Mono<Void> sendNotification() {
return Mono.from(...); // <1> return Mono.from(...); // <1>
} }
---- ----
<1> This raw `Mono` will get decorated with a retry spec. <1> This raw `Mono` will get decorated with a retry spec.
For details on the various characteristics, see the available annotation attributes For details on the various characteristics, see the available annotation attributes in
in {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`]. {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
NOTE: There a `String` variants with placeholder support available for several attributes NOTE: There are `String` variants with placeholder support available for several
as well, as an alternative to the specifically typed annotation attributes used in the attributes as well, as an alternative to the specifically typed annotation attributes used
above examples. in the above examples.
[[resilience-concurrency]] [[resilience-annotations-concurrencylimit]]
== Using `@ConcurrencyLimit` == `@ConcurrencyLimit`
`@ConcurrencyLimit` is an annotation that specifies a concurrency limit for an individual {spring-framework-api}/resilience/annotation/ConcurrencyLimit.html[`@ConcurrencyLimit`] is
method (with the annotation declared at the method level), or for all proxy-invoked an annotation that specifies a concurrency limit for an individual method (with the
methods in a given class hierarchy (with the annotation declared at the type level). annotation declared at the method level), or for all proxy-invoked methods in a given
class hierarchy (with the annotation declared at the type level).
[source,java,indent=0,subs="verbatim,quotes"] [source,java,indent=0,subs="verbatim,quotes"]
---- ----
@ -95,8 +105,8 @@ public void sendNotification() {
---- ----
<1> 1 is the default, but specifying it makes the intent clearer. <1> 1 is the default, but specifying it makes the intent clearer.
Such limiting is particularly useful with Virtual Threads where there is generally Such limiting is particularly useful with Virtual Threads where there is generally no
no thread pool limit in place. For asynchronous tasks, this can be constrained on thread pool limit in place. For asynchronous tasks, this can be constrained on
{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`]. {spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`].
For synchronous invocations, this annotation provides equivalent behavior through For synchronous invocations, this annotation provides equivalent behavior through
{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`] {spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`]
@ -104,12 +114,117 @@ which has been available since Spring Framework 1.0 for programmatic use with th
framework. framework.
[[resilience-enable]] [[resilience-annotations-configuration]]
== Configuring `@EnableResilientMethods` == Enabling Resilient Methods
Note that like many of Spring's core annotation-based features, `@Retryable` and Like many of Spring's core annotation-based features, `@Retryable` and `@ConcurrencyLimit`
`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore. are designed as metadata that you can choose to honor or ignore. The most convenient way
The most convenient way to enable actual processing of the resilience annotations to enable processing of the resilience annotations is to declare
through AOP interception is to declare `@EnableResilientMethods` on a corresponding {spring-framework-api}/resilience/annotation/EnableResilientMethods.html[`@EnableResilientMethods`]
configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor` on a corresponding `@Configuration` class.
and/or `ConcurrencyLimitBeanPostProcessor` individually.
Alternatively, these annotations can be individually enabled by defining a
`RetryAnnotationBeanPostProcessor` or a `ConcurrencyLimitBeanPostProcessor` bean in the
context.
[[resilience-programmatic-retry]]
== Programmatic Retry Support
In contrast to <<resilience-annotations-retryable>> which provides a declarative approach
for specifying retry semantics for methods within beans registered in the
`ApplicationContext`,
{spring-framework-api}/core/retry/RetryTemplate.html[`RetryTemplate`] provides a
programmatic API for retrying arbitrary blocks of code.
Specifically, a `RetryTemplate` executes and potentially retries a
{spring-framework-api}/core/retry/Retryable.html[`Retryable`] operation based on a
configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
[source,java,indent=0,subs="verbatim,quotes"]
----
var retryTemplate = new RetryTemplate(); // <1>
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
<1> Implicitly uses `RetryPolicy.withDefaults()`.
By default, a retryable operation will be retried for any exception thrown: with at most 3
retry attempts after an initial failure, and a delay of 1 second between attempts.
If you only need to customize the number of retry attempts, you can use the
`RetryPolicy.withMaxAttempts()` factory method as demonstrated below.
[source,java,indent=0,subs="verbatim,quotes"]
----
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxAttempts(5)); // <1>
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
<1> Explicitly uses `RetryPolicy.withMaxAttempts(5)`.
If you need to narrow the types of exceptions to retry, that can be achieved via the
`includes()` and `excludes()` builder methods.
[source,java,indent=0,subs="verbatim,quotes"]
----
var retryPolicy = RetryPolicy.builder()
.includes(MessageDeliveryException.class) // <1>
.excludes(...) // <2>
.build();
var retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
<1> Specify one or more exception types to include.
<2> Specify one or more exception types to exclude.
[TIP]
====
For advanced use cases, you can specify a custom `Predicate<Throwable>` via the
`predicate()` method in the `RetryPolicy.Builder`, and the predicate will be used to
determine whether to retry a failed operation based on a given `Throwable` for example,
by checking the cause or the message of the `Throwable`.
Custom predicates can be combined with `includes` and `excludes`; however, custom
predicates will always be applied after `includes` and `excludes` have been applied.
====
The following example demonstrates how to configure a `RetryPolicy` with 5 retry attempts
and an exponential back-off strategy with a bit of jitter.
[source,java,indent=0,subs="verbatim,quotes"]
----
var retryPolicy = RetryPolicy.builder()
.includes(MessageDeliveryException.class)
.maxAttempts(5)
.delay(Duration.ofMillis(100))
.jitter(Duration.ofMillis(10))
.multiplier(2)
.maxDelay(Duration.ofSeconds(1))
.build();
var retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
[TIP]
====
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
with a `RetryTemplate` to react to events published during key retry phases (before a
retry attempt, after a retry attempt, etc.), and you can compose multiple listeners via a
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
====
Although the factory methods and builder API for `RetryPolicy` cover most common
configuration scenarios, you can implement a custom `RetryPolicy` for complete control
over the types of exceptions that should trigger a retry as well as the
{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that you
can also configure a customized `BackOff` strategy via the `backOff()` method in the
`RetryPolicy.Builder`.