Skip to content

Commit 481a74a

Browse files
committed
Add reference documentation for @retryable and @ConcurrencyLimit
See gh-35133 See gh-34529
1 parent 200934c commit 481a74a

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

framework-docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
*** xref:core/aop-api/autoproxy.adoc[]
101101
*** xref:core/aop-api/targetsource.adoc[]
102102
*** xref:core/aop-api/extensibility.adoc[]
103+
** xref:core/resilience.adoc[]
103104
** xref:core/null-safety.adoc[]
104105
** xref:core/databuffer-codec.adoc[]
105106
** xref:core/aot.adoc[]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
[[resilience]]
2+
= Resilience Features
3+
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.
6+
7+
8+
[[resilience-retryable]]
9+
== Using `@Retryable`
10+
11+
`@Retryable` is a common annotation specifying 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).
14+
15+
[source,java,indent=0,subs="verbatim,quotes"]
16+
----
17+
@Retryable
18+
public void sendNotification() {
19+
this.jmsClient.destination("notifications").send(...);
20+
}
21+
----
22+
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 in-between.
25+
26+
This can be specifically adapted for every method if necessary, for example narrowing
27+
the exceptions to retry::
28+
29+
[source,java,indent=0,subs="verbatim,quotes"]
30+
----
31+
@Retryable(MessageDeliveryException.class)
32+
public void sendNotification() {
33+
this.jmsClient.destination("notifications").send(...);
34+
}
35+
----
36+
37+
Or for 5 retry attempts and an exponential back-off strategy with a bit of jitter:
38+
39+
[source,java,indent=0,subs="verbatim,quotes"]
40+
----
41+
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
42+
public void sendNotification() {
43+
this.jmsClient.destination("notifications").send(...);
44+
}
45+
----
46+
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:
49+
50+
[source,java,indent=0,subs="verbatim,quotes"]
51+
----
52+
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
53+
public Mono<Void> sendNotification() {
54+
return Mono.from(...); // this raw Mono will get decorated with a retry spec
55+
}
56+
----
57+
58+
For details on the various characteristics, see the available annotation attributes
59+
on {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`]. Note:
60+
There a String variants with placeholder support available for several attributes
61+
as well, as an alternative to the specifically typed annotation attributes above.
62+
63+
64+
[[resilience-concurrency]]
65+
== Using `@ConcurrencyLimit`
66+
67+
`@ConcurrencyLimit` is an annotation specifying a concurrency limit for an individual
68+
method (with the annotation declared at the method level), or for all proxy-invoked
69+
methods in a given class hierarchy (with the annotation declared at the type level).
70+
71+
[source,java,indent=0,subs="verbatim,quotes"]
72+
----
73+
@ConcurrencyLimit(10)
74+
public void sendNotification() {
75+
this.jmsClient.destination("notifications").send(...);
76+
}
77+
----
78+
79+
This is meant to protect the target resource from being accessed from too many
80+
threads at the same time, similar to the effect of a pool size limit in case of
81+
thread pool or of a connection pool that blocks access if its limit is reached.
82+
83+
At its most constrained, you may set the limit to 1, effectively locking access
84+
to the target bean instance:
85+
86+
[source,java,indent=0,subs="verbatim,quotes"]
87+
----
88+
@ConcurrencyLimit(1) // 1 is the default but this makes it more readable
89+
public void sendNotification() {
90+
this.jmsClient.destination("notifications").send(...);
91+
}
92+
----
93+
94+
Such limiting is particularly useful with Virtual Threads where there is generally
95+
no thread pool limit in place. For asynchronous tasks, this can be constrained on
96+
{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`].
97+
For synchronous invocations, this annotation provides equivalent behavior through
98+
{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`]
99+
which is available since Spring Framework 1.0 for programmatic use with the AOP framework.
100+
101+
102+
[[resilience-enable]]
103+
== Configuring `@EnableResilientMethods`
104+
105+
Note that like many of Spring's core annotation-based features, `@Retryable` and
106+
`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore.
107+
The most convenient way of enabling actual processing of the resilience annotations
108+
through AOP interception is to declare `@EnableResilientMethods` on a corresponding
109+
configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor`
110+
and/or `ConcurrencyLimitBeanPostProcessor` individually.

0 commit comments

Comments
 (0)