Skip to content

Commit 3b68551

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.
1 parent eb59d91 commit 3b68551

18 files changed

+1136
-0
lines changed
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+
/**
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+
@FunctionalInterface
28+
public interface RetryCallback<R> {
29+
30+
/**
31+
* Method to execute and retry if needed.
32+
* @return the result of the callback
33+
* @throws Throwable if an error occurs during the execution of the callback
34+
*/
35+
R run() throws Throwable;
36+
37+
/**
38+
* A unique logical name for this callback to distinguish retries around
39+
* business operations.
40+
* @return the name of the callback. Defaults to the class name.
41+
*/
42+
default String getName() {
43+
return getClass().getName();
44+
}
45+
}
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 may be stateful but 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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.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+
* @param retryExecution the retry execution
36+
*/
37+
default void beforeRetry(RetryExecution retryExecution) {
38+
}
39+
40+
/**
41+
* Called after the first successful retry attempt.
42+
* @param retryExecution the retry execution
43+
* @param result the result of the callback
44+
*/
45+
default void onRetrySuccess(RetryExecution retryExecution, Object result) {
46+
}
47+
48+
/**
49+
* Called every time a retry attempt fails.
50+
* @param retryExecution the retry execution
51+
* @param throwable the throwable thrown by the callback
52+
*/
53+
default void onRetryFailure(RetryExecution retryExecution, Throwable throwable) {
54+
}
55+
56+
/**
57+
* Called once the retry policy is exhausted.
58+
* @param retryExecution the retry execution
59+
* @param throwable the last throwable thrown by the callback
60+
*/
61+
default void onRetryPolicyExhaustion(RetryExecution retryExecution, Throwable throwable) {
62+
}
63+
64+
}
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 extends @Nullable Object> 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)