Skip to content

Commit da8eac3

Browse files
committed
Update RetryPolicySettings to configure exception policy as well
Closes gh-47264
1 parent cb51a44 commit da8eac3

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

core/spring-boot/src/main/java/org/springframework/boot/retry/RetryPolicySettings.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
package org.springframework.boot.retry;
1818

1919
import java.time.Duration;
20+
import java.util.ArrayList;
21+
import java.util.List;
2022
import java.util.function.Function;
23+
import java.util.function.Predicate;
2124

2225
import org.jspecify.annotations.Nullable;
2326

@@ -53,6 +56,12 @@ public final class RetryPolicySettings {
5356
*/
5457
public static final Duration DEFAULT_MAX_DELAY = Duration.ofMillis(RetryPolicy.Builder.DEFAULT_MAX_DELAY);
5558

59+
private List<Class<? extends Throwable>> exceptionIncludes = new ArrayList<>();
60+
61+
private List<Class<? extends Throwable>> exceptionExcludes = new ArrayList<>();
62+
63+
private @Nullable Predicate<Throwable> exceptionPredicate;
64+
5665
private Long maxAttempts = DEFAULT_MAX_ATTEMPTS;
5766

5867
private Duration delay = DEFAULT_DELAY;
@@ -72,6 +81,9 @@ public final class RetryPolicySettings {
7281
public RetryPolicy createRetryPolicy() {
7382
PropertyMapper map = PropertyMapper.get();
7483
RetryPolicy.Builder builder = RetryPolicy.builder();
84+
map.from(this::getExceptionIncludes).to(builder::includes);
85+
map.from(this::getExceptionExcludes).to(builder::excludes);
86+
map.from(this::getExceptionPredicate).to(builder::predicate);
7587
map.from(this::getMaxAttempts).to(builder::maxAttempts);
7688
map.from(this::getDelay).to(builder::delay);
7789
map.from(this::getJitter).to(builder::jitter);
@@ -80,6 +92,64 @@ public RetryPolicy createRetryPolicy() {
8092
return (this.factory != null) ? this.factory.apply(builder) : builder.build();
8193
}
8294

95+
/**
96+
* Return the applicable exception types to attempt a retry for.
97+
* <p>
98+
* The default is empty, leading to a retry attempt for any exception.
99+
* @return the applicable exception types
100+
*/
101+
public List<Class<? extends Throwable>> getExceptionIncludes() {
102+
return this.exceptionIncludes;
103+
}
104+
105+
/**
106+
* Replace the applicable exception types to attempt a retry for by the given
107+
* {@code includes}. Alternatively consider using {@link #getExceptionIncludes()} to
108+
* mutate the existing list.
109+
* @param includes the applicable exception types
110+
*/
111+
public void setExceptionIncludes(List<Class<? extends Throwable>> includes) {
112+
this.exceptionIncludes = new ArrayList<>(includes);
113+
}
114+
115+
/**
116+
* Return the non-applicable exception types to avoid a retry for.
117+
* <p>
118+
* The default is empty, leading to a retry attempt for any exception.
119+
* @return the non-applicable exception types
120+
*/
121+
public List<Class<? extends Throwable>> getExceptionExcludes() {
122+
return this.exceptionExcludes;
123+
}
124+
125+
/**
126+
* Replace the non-applicable exception types to attempt a retry for by the given
127+
* {@code excludes}. Alternatively consider using {@link #getExceptionExcludes()} to
128+
* mutate the existing list.
129+
* @param excludes the non-applicable types
130+
*/
131+
public void setExceptionExcludes(List<Class<? extends Throwable>> excludes) {
132+
this.exceptionExcludes = new ArrayList<>(excludes);
133+
}
134+
135+
/**
136+
* Return the predicate to use to determine whether to retry a failed operation based
137+
* on a given {@link Throwable}.
138+
* @return the predicate to use
139+
*/
140+
public @Nullable Predicate<Throwable> getExceptionPredicate() {
141+
return this.exceptionPredicate;
142+
}
143+
144+
/**
145+
* Set the predicate to use to determine whether to retry a failed operation based on
146+
* a given {@link Throwable}.
147+
* @param exceptionPredicate the predicate to use
148+
*/
149+
public void setExceptionPredicate(@Nullable Predicate<Throwable> exceptionPredicate) {
150+
this.exceptionPredicate = exceptionPredicate;
151+
}
152+
83153
/**
84154
* Return the maximum number of retry attempts.
85155
* @return the maximum number of retry attempts

core/spring-boot/src/test/java/org/springframework/boot/retry/RetryPolicySettingsTests.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package org.springframework.boot.retry;
1818

1919
import java.time.Duration;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.function.Predicate;
2023

2124
import org.junit.jupiter.api.Test;
2225

@@ -25,6 +28,8 @@
2528
import org.springframework.util.backoff.ExponentialBackOff;
2629

2730
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.BDDMockito.given;
32+
import static org.mockito.BDDMockito.then;
2833
import static org.mockito.Mockito.mock;
2934

3035
/**
@@ -34,6 +39,78 @@
3439
*/
3540
class RetryPolicySettingsTests {
3641

42+
@Test
43+
void exceptionIncludesCanBeReplaced() {
44+
RetryPolicySettings settings = new RetryPolicySettings();
45+
settings.getExceptionIncludes().add(IllegalStateException.class);
46+
settings.setExceptionIncludes(List.of(IllegalArgumentException.class));
47+
assertThat(settings.getExceptionIncludes()).containsExactly(IllegalArgumentException.class);
48+
}
49+
50+
@Test
51+
void exceptionIncludesListIsCopied() {
52+
RetryPolicySettings settings = new RetryPolicySettings();
53+
List<Class<? extends Throwable>> includes = new ArrayList<>();
54+
includes.add(IllegalStateException.class);
55+
settings.setExceptionIncludes(includes);
56+
includes.add(IllegalArgumentException.class);
57+
assertThat(settings.getExceptionIncludes()).containsExactly(IllegalStateException.class);
58+
}
59+
60+
@Test
61+
void createRetryPolicyWithExceptionIncludes() {
62+
RetryPolicySettings settings = new RetryPolicySettings();
63+
settings.getExceptionIncludes().add(IllegalStateException.class);
64+
RetryPolicy retryPolicy = settings.createRetryPolicy();
65+
assertThat(retryPolicy.shouldRetry(new IllegalStateException("test"))).isTrue();
66+
assertThat(retryPolicy.shouldRetry(new IllegalArgumentException("test"))).isFalse();
67+
}
68+
69+
@Test
70+
void exceptionExcludesCanBeReplaced() {
71+
RetryPolicySettings settings = new RetryPolicySettings();
72+
settings.getExceptionExcludes().add(IllegalStateException.class);
73+
settings.setExceptionExcludes(List.of(IllegalArgumentException.class));
74+
assertThat(settings.getExceptionExcludes()).containsExactly(IllegalArgumentException.class);
75+
}
76+
77+
@Test
78+
void exceptionExcludesListIsCopied() {
79+
RetryPolicySettings settings = new RetryPolicySettings();
80+
List<Class<? extends Throwable>> excludes = new ArrayList<>();
81+
excludes.add(IllegalStateException.class);
82+
settings.setExceptionExcludes(excludes);
83+
excludes.add(IllegalArgumentException.class);
84+
assertThat(settings.getExceptionExcludes()).containsExactly(IllegalStateException.class);
85+
}
86+
87+
@Test
88+
void createRetryPolicyWithExceptionExcludes() {
89+
RetryPolicySettings settings = new RetryPolicySettings();
90+
settings.getExceptionExcludes().add(IllegalStateException.class);
91+
RetryPolicy retryPolicy = settings.createRetryPolicy();
92+
assertThat(retryPolicy.shouldRetry(new IllegalStateException("test"))).isFalse();
93+
assertThat(retryPolicy.shouldRetry(new IllegalArgumentException("test"))).isTrue();
94+
}
95+
96+
@Test
97+
void getDefaultExceptionPredicate() {
98+
assertThat(new RetryPolicySettings().getExceptionPredicate()).isNull();
99+
}
100+
101+
@Test
102+
void createRetryPolicyWithExceptionPredicate() {
103+
IllegalArgumentException exception = new IllegalArgumentException("test");
104+
Predicate<Throwable> exceptionPredicate = mock();
105+
given(exceptionPredicate.test(exception)).willReturn(true);
106+
RetryPolicySettings settings = new RetryPolicySettings();
107+
settings.setExceptionPredicate(exceptionPredicate);
108+
RetryPolicy retryPolicy = settings.createRetryPolicy();
109+
assertThat(retryPolicy.shouldRetry(exception)).isTrue();
110+
then(exceptionPredicate).should().test(exception);
111+
then(exceptionPredicate).shouldHaveNoMoreInteractions();
112+
}
113+
37114
@Test
38115
void createRetryPolicyWithDefaultsMatchesBackOffDefaults() {
39116
RetryPolicy defaultRetryPolicy = RetryPolicy.builder().build();

0 commit comments

Comments
 (0)