Skip to content

Commit b7a7ca6

Browse files
committed
Introduce a minimal retry functionality as a core framework feature
This commit introduces a minimal core retry feature. It is inspired by Spring Retry, but redesigned and trimmed to the bare minimum to cover most cases. It also adapts the util backoff package with some naming conventions for consistency with the new core retry feature.
1 parent eb59d91 commit b7a7ca6

33 files changed

+1707
-375
lines changed

framework-docs/modules/ROOT/pages/integration/jms/using.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ with a non-durable subscription with high loads may lead to message loss. Make s
234234
use a proper cache level in such a case.
235235

236236
This container also has recoverable capabilities when the broker goes down. By default,
237-
a simple `BackOff` implementation retries every five seconds. You can specify
238-
a custom `BackOff` implementation for more fine-grained recovery options. See
237+
a simple `BackOffPolicy` implementation retries every five seconds. You can specify
238+
a custom `BackOffPolicy` implementation for more fine-grained recovery options. See
239239
{spring-framework-api}/util/backoff/ExponentialBackOff.html[`ExponentialBackOff`] for an example.
240240

241241
NOTE: Like its sibling (xref:integration/jms/using.adoc#jms-mdp-simple[`SimpleMessageListenerContainer`]),
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.retry;
18+
19+
/**
20+
* Callback interface for a retryable piece of code. Used in conjunction with {@link RetryOperations}.
21+
*
22+
* @author Mahmoud Ben Hassine
23+
* @since 7.0
24+
* @param <R> the type of the result
25+
* @see RetryOperations
26+
*/
27+
public interface RetryCallback<R> {
28+
29+
/**
30+
* Method to execute and retry if needed.
31+
* @return the result of the callback
32+
* @throws Throwable if an error occurs during the execution of the callback
33+
*/
34+
R run() throws Throwable;
35+
36+
/**
37+
* A unique logical name for this callback to distinguish retries around
38+
* business operations.
39+
* @return the name of the callback
40+
*/
41+
String getName();
42+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.retry;
18+
19+
import java.io.Serial;
20+
21+
/**
22+
* Exception class for exhausted retries.
23+
*
24+
* @author Mahmoud Ben Hassine
25+
* @since 7.0
26+
* @see RetryOperations
27+
*/
28+
public class RetryException extends Exception {
29+
30+
@Serial
31+
private static final long serialVersionUID = 5439915454935047936L;
32+
33+
/**
34+
* Create a new exception with a message.
35+
* @param message the exception's message
36+
*/
37+
public RetryException(String message) {
38+
super(message);
39+
}
40+
41+
/**
42+
* Create a new exception with a message and a cause.
43+
* @param message the exception's message
44+
* @param cause the exception's cause
45+
*/
46+
public RetryException(String message, Throwable cause) {
47+
super(message, cause);
48+
}
49+
50+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.retry;
18+
19+
/**
20+
* Strategy interface to define a retry execution.
21+
*
22+
* <p>Implementations do not need to be thread-safe.
23+
*
24+
* @author Mahmoud Ben Hassine
25+
* @since 7.0
26+
*/
27+
public interface RetryExecution {
28+
29+
/**
30+
* Specify if the operation should be retried based on the given throwable.
31+
* @param throwable the exception that caused the operation to fail
32+
* @return {@code true} if the operation should be retried, {@code false} otherwise
33+
*/
34+
boolean shouldRetry(Throwable throwable);
35+
36+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.retry;
18+
19+
import org.springframework.core.retry.support.listener.CompositeRetryListener;
20+
21+
/**
22+
* An extension point that allows to inject code during key retry phases.
23+
*
24+
* <p>Typically registered in a {@link RetryTemplate}, and can be composed using
25+
* a {@link CompositeRetryListener}.
26+
*
27+
* @author Mahmoud Ben Hassine
28+
* @since 7.0
29+
* @see CompositeRetryListener
30+
*/
31+
public interface RetryListener {
32+
33+
/**
34+
* Called before every retry attempt.
35+
*/
36+
default void beforeRetry(RetryExecution retryExecution) {
37+
38+
}
39+
40+
/**
41+
* Called after the first successful retry attempt.
42+
* @param result the result of the callback
43+
* @param <T> the type of the result
44+
*/
45+
default <T> void onRetrySuccess(RetryExecution retryExecution, T result) {
46+
}
47+
48+
/**
49+
* Called every time a retry attempt fails.
50+
* @param throwable the throwable thrown by the callback
51+
*/
52+
default void onRetryFailure(RetryExecution retryExecution, Throwable throwable) {
53+
}
54+
55+
/**
56+
* Called once the retry policy is exhausted.
57+
* @param throwable the last throwable thrown by the callback
58+
*/
59+
default void onRetryPolicyExhaustion(RetryExecution retryExecution, Throwable throwable) {
60+
}
61+
62+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.retry;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
/**
22+
* Main entry point to the core retry functionality. Defines a set of retryable operations.
23+
*
24+
* <p>Implemented by {@link RetryTemplate}. Not often used directly, but a useful
25+
* option to enhance testability, as it can easily be mocked or stubbed.
26+
*
27+
* @author Mahmoud Ben Hassine
28+
* @since 7.0
29+
* @see RetryTemplate
30+
*/
31+
public interface RetryOperations {
32+
33+
/**
34+
* Retry the given callback (according to the retry policy configured at the implementation level)
35+
* until it succeeds or eventually throw an exception if the retry policy is exhausted.
36+
* @param retryCallback the callback to call initially and retry if needed
37+
* @param <R> the type of the callback's result
38+
* @return the callback's result
39+
* @throws RetryException thrown if the retry policy is exhausted. All attempt exceptions
40+
* should be added as suppressed exceptions to the final exception.
41+
*/
42+
<R> @Nullable R execute(RetryCallback<R> retryCallback) throws RetryException;
43+
44+
}
45+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.retry;
18+
19+
/**
20+
* Strategy interface to define a retry policy.
21+
*
22+
* @author Mahmoud Ben Hassine
23+
* @since 7.0
24+
*/
25+
public interface RetryPolicy {
26+
27+
/**
28+
* Start a new retry execution.
29+
* @return a fresh {@link RetryExecution} ready to be used
30+
*/
31+
RetryExecution start();
32+
33+
}

0 commit comments

Comments
 (0)