Skip to content

Commit bce44b0

Browse files
committed
Document programmatic retry support in the reference manual
Closes gh-35436
1 parent 7484b9c commit bce44b0

File tree

1 file changed

+148
-33
lines changed

1 file changed

+148
-33
lines changed
Lines changed: 148 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
[[resilience]]
22
= Resilience Features
33

4-
As of 7.0, the core Spring Framework includes a couple of common resilience features,
5-
in particular `@Retryable` and `@ConcurrencyLimit` annotations for method invocations.
4+
As of 7.0, the core Spring Framework includes common resilience features, in particular
5+
<<resilience-annotations-retryable>> and <<resilience-annotations-concurrencylimit>>
6+
annotations for method invocations as well as <<resilience-programmatic-retry,
7+
programmatic retry support>>.
68

79

8-
[[resilience-retryable]]
9-
== Using `@Retryable`
10+
[[resilience-annotations-retryable]]
11+
== `@Retryable`
1012

11-
`@Retryable` is a common annotation that specifies retry characteristics for an individual
12-
method (with the annotation declared at the method level), or for all proxy-invoked
13-
methods in a given class hierarchy (with the annotation declared at the type level).
13+
{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`] is an annotation
14+
that specifies retry characteristics for an individual method (with the annotation
15+
declared at the method level), or for all proxy-invoked methods in a given class hierarchy
16+
(with the annotation declared at the type level).
1417

1518
[source,java,indent=0,subs="verbatim,quotes"]
1619
----
@@ -20,8 +23,8 @@ public void sendNotification() {
2023
}
2124
----
2225

23-
By default, the method invocation will be retried for any exception thrown: with at
24-
most 3 retry attempts after an initial failure, and a delay of 1 second between attempts.
26+
By default, the method invocation will be retried for any exception thrown: with at most 3
27+
retry attempts after an initial failure, and a delay of 1 second between attempts.
2528

2629
This can be specifically adapted for every method if necessary – for example, by narrowing
2730
the exceptions to retry:
@@ -38,38 +41,45 @@ Or for 5 retry attempts and an exponential back-off strategy with a bit of jitte
3841

3942
[source,java,indent=0,subs="verbatim,quotes"]
4043
----
41-
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
44+
@Retryable(
45+
includes = MessageDeliveryException.class,
46+
maxAttempts = 5,
47+
delay = 100,
48+
jitter = 10,
49+
multiplier = 2,
50+
maxDelay = 1000)
4251
public void sendNotification() {
4352
this.jmsClient.destination("notifications").send(...);
4453
}
4554
----
4655

47-
Last but not least, `@Retryable` also works for reactive methods with a reactive
48-
return type, decorating the pipeline with Reactor's retry capabilities:
56+
Last but not least, `@Retryable` also works for reactive methods with a reactive return
57+
type, decorating the pipeline with Reactor's retry capabilities:
4958

5059
[source,java,indent=0,subs="verbatim,quotes"]
5160
----
52-
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
61+
@Retryable(maxAttempts = 5, delay = 100)
5362
public Mono<Void> sendNotification() {
5463
return Mono.from(...); // <1>
5564
}
5665
----
5766
<1> This raw `Mono` will get decorated with a retry spec.
5867

59-
For details on the various characteristics, see the available annotation attributes
60-
in {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
68+
For details on the various characteristics, see the available annotation attributes in
69+
{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
6170

62-
NOTE: There a `String` variants with placeholder support available for several attributes
63-
as well, as an alternative to the specifically typed annotation attributes used in the
64-
above examples.
71+
NOTE: There are `String` variants with placeholder support available for several
72+
attributes as well, as an alternative to the specifically typed annotation attributes used
73+
in the above examples.
6574

6675

67-
[[resilience-concurrency]]
68-
== Using `@ConcurrencyLimit`
76+
[[resilience-annotations-concurrencylimit]]
77+
== `@ConcurrencyLimit`
6978

70-
`@ConcurrencyLimit` is an annotation that specifies a concurrency limit for an individual
71-
method (with the annotation declared at the method level), or for all proxy-invoked
72-
methods in a given class hierarchy (with the annotation declared at the type level).
79+
{spring-framework-api}/resilience/annotation/ConcurrencyLimit.html[`@ConcurrencyLimit`] is
80+
an annotation that specifies a concurrency limit for an individual method (with the
81+
annotation declared at the method level), or for all proxy-invoked methods in a given
82+
class hierarchy (with the annotation declared at the type level).
7383

7484
[source,java,indent=0,subs="verbatim,quotes"]
7585
----
@@ -95,21 +105,126 @@ public void sendNotification() {
95105
----
96106
<1> 1 is the default, but specifying it makes the intent clearer.
97107

98-
Such limiting is particularly useful with Virtual Threads where there is generally
99-
no thread pool limit in place. For asynchronous tasks, this can be constrained on
108+
Such limiting is particularly useful with Virtual Threads where there is generally no
109+
thread pool limit in place. For asynchronous tasks, this can be constrained on
100110
{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`].
101111
For synchronous invocations, this annotation provides equivalent behavior through
102112
{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`]
103113
which has been available since Spring Framework 1.0 for programmatic use with the AOP
104114
framework.
105115

106116

107-
[[resilience-enable]]
108-
== Configuring `@EnableResilientMethods`
117+
[[resilience-annotations-configuration]]
118+
== Enabling Resilient Methods
109119

110-
Note that like many of Spring's core annotation-based features, `@Retryable` and
111-
`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore.
112-
The most convenient way to enable actual processing of the resilience annotations
113-
through AOP interception is to declare `@EnableResilientMethods` on a corresponding
114-
configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor`
115-
and/or `ConcurrencyLimitBeanPostProcessor` individually.
120+
Like many of Spring's core annotation-based features, `@Retryable` and `@ConcurrencyLimit`
121+
are designed as metadata that you can choose to honor or ignore. The most convenient way
122+
to enable processing of the resilience annotations is to declare
123+
{spring-framework-api}/resilience/annotation/EnableResilientMethods.html[`@EnableResilientMethods`]
124+
on a corresponding `@Configuration` class.
125+
126+
Alternatively, these annotations can be individually enabled by defining a
127+
`RetryAnnotationBeanPostProcessor` or a `ConcurrencyLimitBeanPostProcessor` bean in the
128+
context.
129+
130+
131+
[[resilience-programmatic-retry]]
132+
== Programmatic Retry Support
133+
134+
In contrast to <<resilience-annotations-retryable>> which provides a declarative approach
135+
for specifying retry semantics for methods within beans registered in the
136+
`ApplicationContext`,
137+
{spring-framework-api}/core/retry/RetryTemplate.html[`RetryTemplate`] provides a
138+
programmatic API for retrying arbitrary blocks of code.
139+
140+
Specifically, a `RetryTemplate` executes and potentially retries a
141+
{spring-framework-api}/core/retry/Retryable.html[`Retryable`] operation based on a
142+
configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
143+
144+
[source,java,indent=0,subs="verbatim,quotes"]
145+
----
146+
var retryTemplate = new RetryTemplate(); // <1>
147+
148+
retryTemplate.execute(
149+
() -> jmsClient.destination("notifications").send(...));
150+
----
151+
<1> Implicitly uses `RetryPolicy.withDefaults()`.
152+
153+
By default, a retryable operation will be retried for any exception thrown: with at most 3
154+
retry attempts after an initial failure, and a delay of 1 second between attempts.
155+
156+
If you only need to customize the number of retry attempts, you can use the
157+
`RetryPolicy.withMaxAttempts()` factory method as demonstrated below.
158+
159+
[source,java,indent=0,subs="verbatim,quotes"]
160+
----
161+
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxAttempts(5)); // <1>
162+
163+
retryTemplate.execute(
164+
() -> jmsClient.destination("notifications").send(...));
165+
----
166+
<1> Explicitly uses `RetryPolicy.withMaxAttempts(5)`.
167+
168+
If you need to narrow the types of exceptions to retry, that can be achieved via the
169+
`includes()` and `excludes()` builder methods.
170+
171+
[source,java,indent=0,subs="verbatim,quotes"]
172+
----
173+
var retryPolicy = RetryPolicy.builder()
174+
.includes(MessageDeliveryException.class) // <1>
175+
.excludes(...) // <2>
176+
.build();
177+
178+
var retryTemplate = new RetryTemplate(retryPolicy);
179+
180+
retryTemplate.execute(
181+
() -> jmsClient.destination("notifications").send(...));
182+
----
183+
<1> Specify one or more exception types to include.
184+
<2> Specify one or more exception types to exclude.
185+
186+
[TIP]
187+
====
188+
For advanced use cases, you can specify a custom `Predicate<Throwable>` via the
189+
`predicate()` method in the `RetryPolicy.Builder`, and the predicate will be used to
190+
determine whether to retry a failed operation based on a given `Throwable` – for example,
191+
by checking the cause or the message of the `Throwable`.
192+
193+
Custom predicates can be combined with `includes` and `excludes`; however, custom
194+
predicates will always be applied after `includes` and `excludes` have been applied.
195+
====
196+
197+
The following example demonstrates how to configure a `RetryPolicy` with 5 retry attempts
198+
and an exponential back-off strategy with a bit of jitter.
199+
200+
[source,java,indent=0,subs="verbatim,quotes"]
201+
----
202+
var retryPolicy = RetryPolicy.builder()
203+
.includes(MessageDeliveryException.class)
204+
.maxAttempts(5)
205+
.delay(Duration.ofMillis(100))
206+
.jitter(Duration.ofMillis(10))
207+
.multiplier(2)
208+
.maxDelay(Duration.ofSeconds(1))
209+
.build();
210+
211+
var retryTemplate = new RetryTemplate(retryPolicy);
212+
213+
retryTemplate.execute(
214+
() -> jmsClient.destination("notifications").send(...));
215+
----
216+
217+
[TIP]
218+
====
219+
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
220+
with a `RetryTemplate` to react to events published during key retry phases (before a
221+
retry attempt, after a retry attempt, etc.), and you can compose multiple listeners via a
222+
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
223+
====
224+
225+
Although the factory methods and builder API for `RetryPolicy` cover most common
226+
configuration scenarios, you can implement a custom `RetryPolicy` for complete control
227+
over the types of exceptions that should trigger a retry as well as the
228+
{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that you
229+
can also configure a customized `BackOff` strategy via the `backOff()` method in the
230+
`RetryPolicy.Builder`.

0 commit comments

Comments
 (0)