This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.11! |
Resilience Features
As of 7.0, the core Spring Framework includes common resilience features, in particular
@Retryable
and @ConcurrencyLimit
annotations for method invocations as well as programmatic retry support.
@Retryable
@Retryable
is an annotation
that specifies 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).
@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 between attempts.
This can be specifically adapted for every method if necessary – for example, by narrowing the exceptions to retry:
@Retryable(MessageDeliveryException.class)
public void sendNotification() {
this.jmsClient.destination("notifications").send(...);
}
@Retryable(MessageDeliveryException.class) is a shortcut for
@Retryable(includes = MessageDeliveryException.class) .
|
Or for 5 retry attempts and an exponential back-off strategy with a bit of jitter:
@Retryable(
includes = MessageDeliveryException.class,
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:
@Retryable(maxAttempts = 5, delay = 100)
public Mono<Void> sendNotification() {
return Mono.from(...); (1)
}
1 | This raw Mono will get decorated with a retry spec. |
For details on the various characteristics, see the available annotation attributes in
@Retryable
.
Several attributes in @Retryable have String variants that provide property
placeholder and SpEL support, as an alternative to the specifically typed annotation
attributes used in the above examples.
|
@ConcurrencyLimit
@ConcurrencyLimit
is
an annotation that specifies 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).
@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 for a thread pool or a connection pool that blocks access if its limit is reached.
You may optionally set the limit to 1
, effectively locking access to the target bean
instance:
@ConcurrencyLimit(1)
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
SimpleAsyncTaskExecutor
.
For synchronous invocations, this annotation provides equivalent behavior through
ConcurrencyThrottleInterceptor
which has been available since Spring Framework 1.0 for programmatic use with the AOP
framework.
@ConcurrencyLimit also has a limitString attribute that provides property
placeholder and SpEL support, as an alternative to the int based examples above.
|
Enabling Resilient Methods
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
to enable processing of the resilience annotations is to declare
@EnableResilientMethods
on a corresponding @Configuration
class.
Alternatively, these annotations can be individually enabled by defining a
RetryAnnotationBeanPostProcessor
or a ConcurrencyLimitBeanPostProcessor
bean in the
context.
Programmatic Retry Support
In contrast to @Retryable
which provides a declarative approach
for specifying retry semantics for methods within beans registered in the
ApplicationContext
,
RetryTemplate
provides a
programmatic API for retrying arbitrary blocks of code.
Specifically, a RetryTemplate
executes and potentially retries a
Retryable
operation based on a
configured RetryPolicy
.
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.
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.
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. |
For advanced use cases, you can specify a custom Custom predicates can be combined with |
The following example demonstrates how to configure a RetryPolicy
with 5 retry attempts
and an exponential back-off strategy with a bit of jitter.
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(...));
A |
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
BackOff
strategy to use. Note that you
can also configure a customized BackOff
strategy via the backOff()
method in the
RetryPolicy.Builder
.