Rename maxAttempts to maxRetries in @⁠Retryable and RetryPolicy

Prior to this commit, the maximum number of retry attempts was
configured via @⁠Retryable(maxAttempts = ...),
RetryPolicy.withMaxAttempts(), and RetryPolicy.Builder.maxAttempts().
However, this led to confusion for developers who were unsure if
"max attempts" referred to the "total attempts" (i.e., initial attempt
plus retry attempts) or only the "retry attempts".

To improve the programming model, this commit renames maxAttempts to
maxRetries in @⁠Retryable and RetryPolicy.Builder and renames
RetryPolicy.withMaxAttempts() to RetryPolicy.withMaxRetries(). In
addition, this commit updates the documentation to consistently point
out that total attempts = 1 initial attempt + maxRetries attempts.

Closes gh-35772
This commit is contained in:
Sam Brannen
2025-11-10 11:15:25 +01:00
parent 771517dc36
commit 24590092ef
12 changed files with 134 additions and 97 deletions
@@ -29,17 +29,17 @@ import static org.springframework.core.retry.RetryPolicy.Builder.DEFAULT_DELAY;
import static org.springframework.util.backoff.BackOffExecution.STOP;
/**
* Max attempts {@link RetryPolicy} tests.
* Max retries {@link RetryPolicy} tests.
*
* @author Mahmoud Ben Hassine
* @author Sam Brannen
* @since 7.0
*/
class MaxAttemptsRetryPolicyTests {
class MaxRetriesRetryPolicyTests {
@Test
void maxAttempts() {
var retryPolicy = RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build();
void maxRetries() {
var retryPolicy = RetryPolicy.builder().maxRetries(2).delay(Duration.ZERO).build();
var backOffExecution = retryPolicy.getBackOff().start();
var throwable = mock(Throwable.class);
@@ -55,8 +55,8 @@ class MaxAttemptsRetryPolicyTests {
}
@Test
void maxAttemptsZero() {
var retryPolicy = RetryPolicy.builder().maxAttempts(0).delay(Duration.ZERO).build();
void maxRetriesZero() {
var retryPolicy = RetryPolicy.builder().maxRetries(0).delay(Duration.ZERO).build();
var backOffExecution = retryPolicy.getBackOff().start();
var throwable = mock(Throwable.class);
@@ -67,9 +67,9 @@ class MaxAttemptsRetryPolicyTests {
}
@Test
void maxAttemptsAndPredicate() {
void maxRetriesAndPredicate() {
var retryPolicy = RetryPolicy.builder()
.maxAttempts(4)
.maxRetries(4)
.delay(Duration.ofMillis(1))
.predicate(NumberFormatException.class::isInstance)
.build();
@@ -94,9 +94,9 @@ class MaxAttemptsRetryPolicyTests {
}
@Test
void maxAttemptsWithIncludesAndExcludes() {
void maxRetriesWithIncludesAndExcludes() {
var retryPolicy = RetryPolicy.builder()
.maxAttempts(6)
.maxRetries(6)
.includes(RuntimeException.class, IOException.class)
.excludes(FileNotFoundException.class, CustomFileSystemException.class)
.build();
@@ -64,15 +64,15 @@ class RetryPolicyTests {
}
@Test
void withMaxAttemptsPreconditions() {
void withMaxRetriesPreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RetryPolicy.withMaxAttempts(-1))
.withMessageStartingWith("Invalid maxAttempts (-1)");
.isThrownBy(() -> RetryPolicy.withMaxRetries(-1))
.withMessageStartingWith("Invalid maxRetries (-1)");
}
@Test
void withMaxAttempts() {
var policy = RetryPolicy.withMaxAttempts(5);
void withMaxRetries() {
var policy = RetryPolicy.withMaxRetries(5);
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
assertThat(policy.shouldRetry(new IOException())).isTrue();
@@ -96,7 +96,7 @@ class RetryPolicyTests {
.isThrownBy(() -> RetryPolicy.builder().backOff(mock()).delay(Duration.ofMillis(10)).build())
.withMessage("""
The following configuration options are not supported with a custom BackOff strategy: \
maxAttempts, delay, jitter, multiplier, or maxDelay.""");
maxRetries, delay, jitter, multiplier, or maxDelay.""");
}
@Test
@@ -111,15 +111,15 @@ class RetryPolicyTests {
}
@Test
void maxAttemptsPreconditions() {
void maxRetriesPreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RetryPolicy.builder().maxAttempts(-1))
.withMessageStartingWith("Invalid maxAttempts (-1)");
.isThrownBy(() -> RetryPolicy.builder().maxRetries(-1))
.withMessageStartingWith("Invalid maxRetries (-1)");
}
@Test
void maxAttempts() {
var policy = RetryPolicy.builder().maxAttempts(5).build();
void maxRetries() {
var policy = RetryPolicy.builder().maxRetries(5).build();
assertThat(policy.getBackOff())
.asInstanceOf(type(ExponentialBackOff.class))
@@ -57,7 +57,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
*/
class RetryTemplateTests {
private final RetryPolicy retryPolicy = RetryPolicy.builder().maxAttempts(3).delay(Duration.ZERO).build();
private final RetryPolicy retryPolicy = RetryPolicy.builder().maxRetries(3).delay(Duration.ZERO).build();
private final RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
@@ -116,7 +116,7 @@ class RetryTemplateTests {
@Test
void retryWithInitialFailureAndZeroRetriesFixedBackOffPolicy() {
RetryPolicy retryPolicy = RetryPolicy.withMaxAttempts(0);
RetryPolicy retryPolicy = RetryPolicy.withMaxRetries(0);
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.setRetryListener(retryListener);
@@ -138,7 +138,7 @@ class RetryTemplateTests {
@Test
void retryWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder() {
RetryPolicy retryPolicy = RetryPolicy.builder().maxAttempts(0).build();
RetryPolicy retryPolicy = RetryPolicy.builder().maxRetries(0).build();
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.setRetryListener(retryListener);
@@ -263,7 +263,7 @@ class RetryTemplateTests {
};
var retryPolicy = RetryPolicy.builder()
.maxAttempts(5)
.maxRetries(5)
.delay(Duration.ofMillis(1))
.predicate(NumberFormatException.class::isInstance)
.predicate(t -> t.getMessage().equals("Boom!"))
@@ -311,7 +311,7 @@ class RetryTemplateTests {
};
var retryPolicy = RetryPolicy.builder()
.maxAttempts(Integer.MAX_VALUE)
.maxRetries(Integer.MAX_VALUE)
.delay(Duration.ZERO)
.includes(IOException.class)
.build();
@@ -344,13 +344,13 @@ class RetryTemplateTests {
static final List<ArgumentSet> includesAndExcludesRetryPolicies = List.of(
argumentSet("Excludes",
RetryPolicy.builder()
.maxAttempts(Integer.MAX_VALUE)
.maxRetries(Integer.MAX_VALUE)
.delay(Duration.ZERO)
.excludes(FileNotFoundException.class)
.build()),
argumentSet("Includes & Excludes",
RetryPolicy.builder()
.maxAttempts(Integer.MAX_VALUE)
.maxRetries(Integer.MAX_VALUE)
.delay(Duration.ZERO)
.includes(IOException.class)
.excludes(FileNotFoundException.class)