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 String Retry to implement retry logic in Spring applications. We’ll also configure listeners to receive additional callbacks.
2. Maven Dependencies
Let’s begin by adding the dependency into our pom.xml:
<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.
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
We can make a method call to be retried on failure by using annotations.
4.1. @Retryable
To add retry functionality to methods, @Retryable can be used:
@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
5.1 RetryOperations
Spring Retry provides RetryOperations interface which supplies a set of execute() methods:
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
To run code with retry handling we call the retryTemplate.execute():
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
Spring Retry can be configured by XML using the Spring AOP namespace.
6.1. Adding XML File
In the classpath, let’s add retryadvice.xml:
... <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.
6.2. Using XML Configuration
Import retryadvice.xml from the classpath and enable @AspectJ support:
@Configuration @EnableRetry @EnableAspectJAutoProxy @ImportResource("classpath:/retryadvice.xml") public class AppConfig { ... }
7. Listeners
Listeners provide additional callbacks upon retries. They can be used for various cross-cutting concerns across different retries.
7.1. Adding Callbacks
The callbacks are provided in a RetryListener interface:
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
Next, we register our listener (DefaultListenerSupport) to our RetryTemplate bean:
@Configuration public class AppConfig { ... @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); ... retryTemplate.registerListener(new DefaultListenerSupport()); return retryTemplate; } }
8. Testing the Results
Let’s verify 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.