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(...);
}
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
.
There are String variants with placeholder support available for several
attributes as well, 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) (1)
public void sendNotification() {
this.jmsClient.destination("notifications").send(...);
}
1 | 1 is the default, but specifying it makes the intent clearer. |
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.
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
.