Match against exception causes in @⁠Retryable and RetryPolicy

Prior to this commit, our @⁠Retryable support as well as a RetryPolicy
created by the RetryPolicy.Builder only matched against top-level
exceptions when filtering included/excluded exceptions thrown by a
@⁠Retryable method or Retryable operation.

With this commit, we now match against not only top-level exceptions
but also nested causes within those top-level exceptions. This is
achieved via the new ExceptionTypeFilter.match(Throwable, boolean)
support.

See gh-35592
Closes gh-35583
This commit is contained in:
Sam Brannen
2025-10-09 16:53:23 +02:00
parent 58940794cf
commit 97ae5fde7c
9 changed files with 140 additions and 44 deletions
@@ -40,6 +40,7 @@ import org.springframework.resilience.retry.MethodRetryPredicate;
* project but redesigned as a minimal core retry feature in the Spring Framework.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 7.0
* @see EnableResilientMethods
* @see RetryAnnotationBeanPostProcessor
@@ -64,6 +65,9 @@ public @interface Retryable {
/**
* Applicable exception types to attempt a retry for. This attribute
* allows for the convenient specification of assignable exception types.
* <p>The supplied exception types will be matched against an exception
* thrown by a failed invocation as well as nested
* {@linkplain Throwable#getCause() causes}.
* <p>This can optionally be combined with {@link #excludes() excludes} or
* a custom {@link #predicate() predicate}.
* <p>The default is empty, leading to a retry attempt for any exception.
@@ -76,6 +80,9 @@ public @interface Retryable {
/**
* Non-applicable exception types to avoid a retry for. This attribute
* allows for the convenient specification of assignable exception types.
* <p>The supplied exception types will be matched against an exception
* thrown by a failed invocation as well as nested
* {@linkplain Throwable#getCause() causes}.
* <p>This can optionally be combined with {@link #includes() includes} or
* a custom {@link #predicate() predicate}.
* <p>The default is empty, leading to a retry attempt for any exception.
@@ -65,7 +65,7 @@ public record MethodRetrySpec(
MethodRetryPredicate combinedPredicate() {
ExceptionTypeFilter exceptionFilter = new ExceptionTypeFilter(this.includes, this.excludes);
return (method, throwable) -> exceptionFilter.match(throwable) &&
return (method, throwable) -> exceptionFilter.match(throwable, true) &&
this.predicate.shouldRetry(method, throwable);
}