This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.8! |
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.
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).
@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::
@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(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, jitter = 10, multiplier = 2, maxDelay = 1000)
public Mono<Void> 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 @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.
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).
@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:
@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
SimpleAsyncTaskExecutor
.
For synchronous invocations, this annotation provides equivalent behavior through
ConcurrencyThrottleInterceptor
which is available since Spring Framework 1.0 for programmatic use with the AOP framework.
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.