From 481a74a202d9c4123efcd721cb704750de2d0022 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 10 Jul 2025 17:14:08 +0200 Subject: [PATCH] Add reference documentation for @Retryable and @ConcurrencyLimit See gh-35133 See gh-34529 --- framework-docs/modules/ROOT/nav.adoc | 1 + .../modules/ROOT/pages/core/resilience.adoc | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 framework-docs/modules/ROOT/pages/core/resilience.adoc diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index 02e9bc8dd16..a34c38e8f50 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -100,6 +100,7 @@ *** xref:core/aop-api/autoproxy.adoc[] *** xref:core/aop-api/targetsource.adoc[] *** xref:core/aop-api/extensibility.adoc[] +** xref:core/resilience.adoc[] ** xref:core/null-safety.adoc[] ** xref:core/databuffer-codec.adoc[] ** xref:core/aot.adoc[] diff --git a/framework-docs/modules/ROOT/pages/core/resilience.adoc b/framework-docs/modules/ROOT/pages/core/resilience.adoc new file mode 100644 index 00000000000..3f90d130595 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/core/resilience.adoc @@ -0,0 +1,110 @@ +[[resilience]] += Resilience Features + +As of 7.0, the core Spring Framework includes a couple of common resilience features, +in particular `@Retryable` and `@ConcurrencyLimit` annotations for method invocations. + + +[[resilience-retryable]] +== Using `@Retryable` + +`@Retryable` is a common annotation specifying retry characteristics for an individual +method (with the 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"] +---- +@Retryable +public void sendNotification() { + this.jmsClient.destination("notifications").send(...); +} +---- + +By default, the method invocation will be retried for any exception thrown: with at +most 3 retry attempts after an initial failure, and a delay of 1 second in-between. + +This can be specifically adapted for every method if necessary, for example narrowing +the exceptions to retry:: + +[source,java,indent=0,subs="verbatim,quotes"] +---- +@Retryable(MessageDeliveryException.class) +public void sendNotification() { + this.jmsClient.destination("notifications").send(...); +} +---- + +Or for 5 retry attempts and an exponential back-off strategy with a bit of jitter: + +[source,java,indent=0,subs="verbatim,quotes"] +---- +@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000) +public void sendNotification() { + this.jmsClient.destination("notifications").send(...); +} +---- + +Last but not least, `@Retryable` also works for reactive methods with a reactive +return type, decorating the pipeline with Reactor's retry capabilities: + +[source,java,indent=0,subs="verbatim,quotes"] +---- +@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000) +public Mono sendNotification() { + return Mono.from(...); // this raw Mono will get decorated with a retry spec +} +---- + +For details on the various characteristics, see the available annotation attributes +on {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`]. Note: +There a String variants with placeholder support available for several attributes +as well, as an alternative to the specifically typed annotation attributes above. + + +[[resilience-concurrency]] +== Using `@ConcurrencyLimit` + +`@ConcurrencyLimit` is an annotation specifying a concurrency limit for an individual +method (with the 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"] +---- +@ConcurrencyLimit(10) +public void sendNotification() { + this.jmsClient.destination("notifications").send(...); +} +---- + +This is meant to protect the target resource from being accessed from too many +threads at the same time, similar to the effect of a pool size limit in case of +thread pool or of a connection pool that blocks access if its limit is reached. + +At its most constrained, you may set the limit to 1, effectively locking access +to the target bean instance: + +[source,java,indent=0,subs="verbatim,quotes"] +---- +@ConcurrencyLimit(1) // 1 is the default but this makes it more readable +public void sendNotification() { + this.jmsClient.destination("notifications").send(...); +} +---- + +Such limiting is particularly useful with Virtual Threads where there is generally +no thread pool limit in place. For asynchronous tasks, this can be constrained on +{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`]. +For synchronous invocations, this annotation provides equivalent behavior through +{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`] +which is available since Spring Framework 1.0 for programmatic use with the AOP framework. + + +[[resilience-enable]] +== Configuring `@EnableResilientMethods` + +Note that like many of Spring's core annotation-based features, `@Retryable` and +`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore. +The most convenient way of enabling actual processing of the resilience annotations +through AOP interception is to declare `@EnableResilientMethods` on a corresponding +configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor` +and/or `ConcurrencyLimitBeanPostProcessor` individually.