Did a method just throw an Exception? Well, we could re-invoke it a few times more before giving up using Spring’s Retry API. Last time we touched on BackOff and related interfaces and classes which is used in this API.
Requirements
Stuff used in this post.
- Spring Boot 1.5.4.RELEASE
- spring-retry
- we require this Maven dependency in our
pom.xml123456...<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency>...
- we require this Maven dependency in our
pom.xml
- Java 8
@Retryable
Although there is this declarative option, we’ll not cover that here. It’s usage may be more related to Spring Batch as we are unable to override that annotation’s attributes. For instance, setting maxAttempts like in the codes below don’t work and @Retryable defaults to maxAttemptes = 3 .
[wp_ad_camp_5]
1 2 3 4 5 6 | @Retryable(value = { Exception.class }, maxAttempts = 5, backoff = @Backoff(delay = 5000)) public List<String> getPersonIds() throws Exception { throw new Exception("Service not available at the moment"); } |
Using the Retry API
There are essentially 4 things to consider when using this API:
- A method invocation that may fail.
- A method invocation to recover from a failure after n retries.
- Methods’ return values
- Using RetryTemplate class
Our Sample Codes
This is our service class. It has several methods for test with the API.
- Methods with return value
-
getAllCodes_ThrowException
- Always throws an Exception
-
getAllCodes_Recovery
- Recovery method
-
getAllCodes_Ok
- Does not throw Exceptions
- Always works and returns a list of String objects
-
getAllCodes_ThrowException
- Methods with void return type
-
updateCode_ThrowException
- Always throws an Exception
-
updateCode_Recovery
- Recovery method
-
updateCode_Ok
- Does not throw Exceptions
-
updateCode_ThrowException
[wp_ad_camp_4]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | @Service public class AnyService { List<String> data = Stream.of("AB", "AC", "XY").collect(Collectors.toList()); public List<String> getAllCodes_ThrowException() throws Exception { throw new Exception("Service not available at this time!"); } public List<String> getAllCodes_Recovery() throws Exception { System.out.println("Recovered!"); return Collections.EMPTY_LIST; } public List<String> getAllCodes_Ok() { System.out.println("Successfully retrieved data!"); return data; } public void updateCode_Ok(String code, String status) { if(!data.contains(code)) { throw new RuntimeException("Code not found!"); } System.out.println("Code status successfully updated!"); } public void updateCode_Recovery(String code, String status) throws Exception { System.out.println("Recovered!"); } public void updateCode_ThrowException(String code, String status) throws Exception { throw new Exception("Service not available at this time!"); } } |
Next is our @Configuration class. Notice I commented out stuff related to FixedBackOffPolicy and used ExponentialBackOffPolicy. Either one can be used interchangeably depending on our requirements.
[wp_ad_camp_3]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | @Configuration public class CustomConfiguration { @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); /* Start - Fixed BackOff */ // FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); // fixedBackOffPolicy.setBackOffPeriod(2000l); // retryTemplate.setBackOffPolicy(fixedBackOffPolicy); /* End - Fixed BackOff */ ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy(); exponentialBackOffPolicy.setMultiplier(1.5); exponentialBackOffPolicy.setInitialInterval(10000); // 10 seconds exponentialBackOffPolicy.setMaxInterval(30000); // 30 seconds max retryTemplate.setBackOffPolicy(exponentialBackOffPolicy); //SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); //retryPolicy.setMaxAttempts(2); //retryTemplate.setRetryPolicy(retryPolicy); return retryTemplate; } } |
Lastly, our main class. We’ll place the actual codes to test between /** Start **/ and /** End **/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @SpringBootApplication public class ComTurretaSpringbootRetryApplication { public static void main(String[] args) throws Exception { ApplicationContext context = SpringApplication.run(ComTurretaSpringbootRetryApplication.class, args); AnyService anyService = context.getBean(AnyService.class); RetryTemplate retryTemplate = context.getBean(RetryTemplate.class); /** Start **/ /** End **/ } } |
Testing
With Return Value, Exception and Recovery method
[wp_ad_camp_2]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | List<String> data = retryTemplate.execute( new RetryCallback<List<String>, Exception>() { @Override public List<String> doWithRetry(RetryContext retryContext) throws Exception { System.out.println("Trying..."); return anyService.getAllCodes_ThrowException(); } }, new RecoveryCallback<List<String>>() { @Override public List<String> recover(RetryContext retryContext) throws Exception { return Collections.EMPTY_LIST; } }); // Empty list - no output data.forEach(System.out::print); |
With Return Value, Recovery Method, No Exception
1 2 3 4 5 6 7 8 9 10 11 12 13 | List<String> dataTwo = retryTemplate.execute( // Retries retryContext -> { System.out.println("Trying..."); return anyService.getAllCodes_Ok(); } , // Recover retryContext -> Collections.EMPTY_LIST ); // Successfully retrieved data dataTwo.forEach(System.out::println); |
Outputs:
1 2 3 4 5 | Trying... Successfully retrieved data! AB AC XY |
Void Return Type, Exception, and Recovery Method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Return null because service method has void return type retryTemplate.execute( // Retries retryContext -> { System.out.println("Trying..."); anyService.updateCode_ThrowException("AA", "OK"); return null; }, // Recover retryContext -> { anyService.updateCode_Recovery("AA", "OK-BUT-ERROR"); return null; } ); |
Outputs:
1 2 3 4 | Trying... Trying... Trying... Recovered! |
Void Return Type, Recovery Method, No Exception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | retryTemplate.execute( // Retries retryContext -> { System.out.println("Trying..."); anyService.updateCode_Ok("AB", "OK"); return null; }, // Recover retryContext -> { anyService.updateCode_Recovery("AA", "OK-BUT-ERROR"); return null; } ); |
Outputs:
1 2 | Trying... Code status successfully updated! |
[wp_ad_camp_1]
Download the Codes
https://github.com/Turreta/Using-Spring-Retry-API