spring-retry
Guide to Spring Retry
1. Overview
Spring Retry provides an ability to automatically re-invoke a failed operation. This is helpful where the errors may be transient in nature (like a momentary network glitch). Spring Retry provides declarative control of the process and policy-based behavior that is easy to extend and customize.
In this article, we’ll see how to use Spring Retry to implement retry logic in Spring applications. We’ll also configure listeners to receive additional callbacks.
2. Maven Dependencies
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
We can check the latest version of spring-retry in Maven Central.
We also need to add Spring AOP in our pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
3. Enabling Spring Retry
To enable Spring Retry in an application, we need to add the @EnableRetry annotation to our @Configuration class:
@Configuration
@EnableRetry
public class AppConfig { ... }
4. Retry with Annotations
4.1. @Retryable
@Service
public interface MyService {
@Retryable(
value = { SQLException.class },
maxAttempts = 2,
backoff = @Backoff(delay = 5000))
void retryService(String sql) throws SQLException;
...
}
Here, the retry behavior is customized using the attributes of @Retryable. In this example, retry will be attempted only if the method throws an SQLException. There will be up to 2 retries and a delay of 5000 milliseconds.
If @Retryable is used without any attributes, if the method fails with an exception, then retry will be attempted up to three times, with a delay of one second.
4.2. @Recover
The @Recover annotation is used to define a separate recovery method when a @Retryable method fails with a specified exception:
@Service
public interface MyService {
...
@Recover
void recover(SQLException e, String sql);
}
So if the retryService() method throws an SQLException, the recover() method will be called. A suitable recovery handler has its first parameter of type Throwable (optional). Subsequent arguments are populated from the argument list of the failed method in the same order as the failed method, and with the same return type.
5. RetryTemplate
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
...
}
The RetryCallback which is a parameter of the execute() is an interface that allows insertion of business logic that needs to be retried upon failure:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
5.2. RetryTemplate Configuration
The RetryTemplate is an implementation of the RetryOperations. Let’s configure a RetryTemplate bean in our @Configuration class:
@Configuration
public class AppConfig {
//...
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}
RetryPolicy determines when an operation should be retried. A SimpleRetryPolicy is used to retry a fixed number of times.
BackOffPolicy is used to control back off between retry attempts. A FixedBackOffPolicy pauses for a fixed period of time before continuing.
5.3. Using the RetryTemplate
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
@Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});
The same could be achieved using a lambda expression instead of an anonymous class:_
_
retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});
6. XML Configuration
6.1. Adding XML File
...
<beans>
<aop:config>
<aop:pointcut id="transactional"
expression="execution(*MyService.xmlRetryService(..))" />
<aop:advisor pointcut-ref="transactional"
advice-ref="taskRetryAdvice" order="-1" />
</aop:config>
<bean id="taskRetryAdvice"
class="org.springframework.retry.interceptor.
RetryOperationsInterceptor">
<property name="RetryOperations" ref="taskRetryTemplate" />
</bean>
<bean id="taskRetryTemplate"
class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy" ref="taskRetryPolicy" />
<property name="backOffPolicy" ref="exponentialBackOffPolicy" />
</bean>
<bean id="taskRetryPolicy"
class="org.springframework.retry.policy.SimpleRetryPolicy">
<constructor-arg index="0" value="5" />
<constructor-arg index="1">
<map>
<entry key="java.lang.RuntimeException" value="true" />
</map>
</constructor-arg>
</bean>
<bean id="exponentialBackOffPolicy"
class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="300">
</property>
<property name="maxInterval" value="30000">
</property>
<property name="multiplier" value="2.0">
</property>
</bean>
</beans>
...
This example uses a custom RetryTemplate inside the interceptor of xmlRetryService method.
7. Listeners
Listeners provide additional callbacks upon retries. They can be used for various cross-cutting concerns across different retries.
7.1. Adding Callbacks
public class DefaultListenerSupport extends RetryListenerSupport {
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
logger.info("onClose);
...
super.close(context, callback, throwable);
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
logger.info("onError");
...
super.onError(context, callback, throwable);
}
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
logger.info("onOpen);
...
return super.open(context, callback);
}
}
The open and close callbacks come before and after the entire retry, and onError applies to the individual RetryCallback calls.
7.2. Registering the Listener
@Configuration
public class AppConfig {
...
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
...
retryTemplate.registerListener(new DefaultListenerSupport());
return retryTemplate;
}
}
8. Testing the Results
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = AppConfig.class,
loader = AnnotationConfigContextLoader.class)
public class SpringRetryTest {
@Autowired
private MyService myService;
@Autowired
private RetryTemplate retryTemplate;
@Test(expected = RuntimeException.class)
public void givenTemplateRetryService_whenCallWithException_thenRetry() {
retryTemplate.execute(arg0 -> {
myService.templateRetryService();
return null;
});
}
}
When we run the test case, the below log text means that we have successfully configured the RetryTemplate and Listener:
2017-01-09 20:04:10 [main] INFO o.b.s.DefaultListenerSupport - onOpen
2017-01-09 20:04:10 [main] INFO o.baeldung.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService()
2017-01-09 20:04:10 [main] INFO o.b.s.DefaultListenerSupport - onError
2017-01-09 20:04:12 [main] INFO o.baeldung.springretry.MyServiceImpl
- throw RuntimeException in method templateRetryService()
2017-01-09 20:04:12 [main] INFO o.b.s.DefaultListenerSupport - onError
2017-01-09 20:04:12 [main] INFO o.b.s.DefaultListenerSupport - onClose
9. Conclusion
In this article, we have introduced Spring Retry. We have seen examples of retry using annotations and RetryTemplate. We have then configured additional callbacks using listeners.
You can find the source code for this article over on GitHub.