The new Certification Class of Learn Spring Security is out:

>> CHECK OUT THE COURSE

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.

Go deeper into Spring Security with the course:

>> LEARN SPRING SECURITY