I have a method with the following signature:
public Optional<String> doSomething() {
...
}
If I get an empty Optional
I'd like to retry this method and only after 3 times return the empty Optional
.
I've looked and found the Retryable
spring annotation, but it seems to only work on Exceptions.
If possible I'd like to use a library for this, and avoid:
1. Simple for-loop with try-catch. A simple solution to implement retry logic in Java is to write your code inside a for loop that executes the specified number of times (the maximum retry value).
A retryable exception is a transient exception that if retried may succeed.
First, you need to enable Spring Retry. You can achieve this by adding the @EnableRetry annotation to your @SpringBootApplication or @Configuration class. You can now use @Retryable to annotate any method to be a candidate or retry and @Recover to specify fallback methods.
Spring Retry provides the ability to automatically re-invoke a failed operation. This is helpful when errors may be transient in nature. For example, a momentary network glitch, network outage, server down, or deadlock. You can configure the. spring-retry.
I have been using failsafe build in retry. You can retry based on predicates and exceptions.
Your code would look like this:
private Optional<String> doSomethingWithRetry() {
RetryPolicy<Optional> retryPolicy = new RetryPolicy<Optional>()
.withMaxAttempts(3)
.handleResultIf(result -> {
System.out.println("predicate");
return !result.isPresent();
});
return Failsafe
.with(retryPolicy)
.onSuccess(response -> System.out.println("ok"))
.onFailure(response -> System.out.println("no ok"))
.get(() -> doSomething());
}
private Optional<String> doSomething() {
return Optional.of("result");
}
If the optional is not empty the output is:
predicate
ok
Otherwise looks like:
predicate
predicate
predicate
no ok
@Retryable
(and the underlying RetryTemplate
) are purely based on exceptions.
You could subclass RetryTemplate
, overriding doExecute()
to check the return value.
You would probably have to replicate much of the code in the method; it's not really designed for overriding just the retryCallback.doWithRetry()
call.
You can use a custom RetryTemplate
in a RetryOperationsInterceptor
(specified in the @Retryable
in the interceptor
property).
EDIT
The current RetryTemplate
code looks like this...
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
// Reset the last exception, so if we are successful
// the close interceptors will not think we failed...
lastException = null;
return retryCallback.doWithRetry(context);
}
catch (Throwable e) {
lastException = e;
try {
registerThrowable(retryPolicy, state, context, e);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable",
ex);
}
finally {
doOnErrorInterceptors(retryCallback, context, e);
}
...
}
You would need to change it to something like...
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
// Reset the last exception, so if we are successful
// the close interceptors will not think we failed...
lastException = null;
T result = retryCallback.doWithRetry(context);
if (((Optional<String>) result).get() == null) {
try {
registerThrowable(retryPolicy, state, context, someDummyException);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable",
ex);
}
finally {
doOnErrorInterceptors(retryCallback, context, e);
}
...
}
else {
return result;
}
}
catch (Throwable e) {
...
}
Where someDummyException
is to fool the context into incrementing the counter. It can be a static
field, just created once.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With