1/30/15

Spring Retry Sample

A few days ago, I needed to build a retry mechanism in my project. I could get network exceptions while invoking a web service. Typically, I planned to put my code snippet in a "while loop", define a counter variable and controls the loop with maximum retry count condition.

Suddenly I claimed that it should be a different structure or a good pattern for the retry. I searched on the Internet and encountered Spring Retry library. I could not find a good tutorial or sample, but I implemented my own code with the help of simple examples in the Spring Batch documentation 

For the Spring Retry library, you can add the dependency below into your pom.xml file:
<dependency>
   <groupid>org.springframework.retry</groupid>
   <artifactid>spring-retry</artifactid>
   <version>1.1.2.RELEASE</version> 
</dependency>
Spring Retry helped me in my project. I am overjoyed to use it. Now I would like to share my know-how with a sample application. In my real-world applications, I regularly establish a retry mechanism in the web service clients in order to make more robust processing. Web service servers sometimes can't response in a while because of the network bottlenecks or instantaneous internal server errors. So the current transaction fails. In the sample application, I did not indicate a web service client implementation. To keep simple, I simulated it:

@Service
public class WebServiceClientSimulation {

 
private static final String  SIMULATION_RETRY_STR  = "yes";

 
@Autowired
 
Environment          env;

 
public String invoke() throws ApplicationException {

   
// Simulate the implementation
   
System.out.println( "Web Service was called and executed" );

   
if ( env.getRequiredProperty( "simulation.retry" ).equals( SIMULATION_RETRY_STR ) ) {

     
// An internal error has been occurred
     
throw new ApplicationException();
   
}

   
return "success";
 
}
}

In the Spring Retry, the main Interface class is RetryOperations. The simplest general purpose implementation of RetryOperations is RetryTemplate. The retry system is configured with the RetryTemplate class. BackoffPolicy class determines the waiting rules when a failure happens. I use FixedBackOffPolicy subclass. With this policy, fixed time interval is set between two retries. Inside a RetryTemplate, the decision to retry or fail in the execute method is determined by a RetryPolicy. SimpleRetryPolicy subclass of the RetryPolicy just allows a retry on any of a named list of exception types. In my example, I configured the SimpleRetryPolicy with a fixed retry max count. ExceptionClassifierRetryPolicy class allows to configure different retry behavior for an arbitrary set of exception types. In my sample, I decide to match the "SimpleRetryPolicy" with my custom ApplicationException class. Exception class and its RetryPolicy counterpart are kept in a map. Then this map is set to ExceptionClassifierRetryPolicy class as a general policy for all the Exceptions. With the help of the policy map, you can determine the retry policy for each exception type.

@Configuration
@ComponentScan
@PropertySource
( "classpath:application.properties" )
public class ApplicationContext {

 
@Bean
 
public FixedBackOffPolicy getBackOffPolicy( final Environment env ) {

   
final FixedBackOffPolicy policy = new FixedBackOffPolicy();
    policy.setBackOffPeriod
( Long.valueOf( env.getRequiredProperty( "retry.interval" ) ) );

   
return policy;
 
}

 
@Bean
 
public ExceptionClassifierRetryPolicy getRetryPolicy( final Environment env ) {

   
final Map< Class< ? extends Throwable >, RetryPolicy > policyMap =
     
new HashMap< Class< ? extends Throwable >, RetryPolicy >();

   
final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
    simpleRetryPolicy.setMaxAttempts
( Integer.valueOf( env.getRequiredProperty( "retry.count" ) ) );

   
// Determine the policy per exception
   
policyMap.put( ApplicationException.class, simpleRetryPolicy );

   
final ExceptionClassifierRetryPolicy retryPolicy = new ExceptionClassifierRetryPolicy();
    retryPolicy.setPolicyMap
( policyMap );

   
return retryPolicy;
 
}

 
@Bean
 
public WebServiceClientSimulation getWebServiceClient() {
   
return new WebServiceClientSimulation();
 
}
}

I put the application variables in a property file. "Simulation.retry" property controls and decides the test scenario. If the value of this property is "yes", An exception is thrown in the "WebServiceClientSimulation" class, otherwise "success" response is returned and no exception is occurs and RetryTemplate does not produce any retry. So I can test both success case and failure and retrying case. Property file content:

retry.interval = 3000
retry.count = 3
simulation.retry = yes

In the ApplicationService class, I instantiated a RetryTemplate variable. I set the BackoffPolicy and RetryPolicy and call the web service client with retry. There are some posibble "execute" overload methods:

 T execute(RetryCallback retryCallback) throws Exception;

 T execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback)
    throws Exception;

 T execute(RetryCallback retryCallback, RetryState retryState)  
    throws Exception, ExhaustedRetryException;

 T execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback,
    RetryState retryState) throws Exception;

I implemented the second one. First is the RetryCallback class and the second is RecoveryCallback class parameters. After every retry struggle, the "doWithRetry" method of the RetryCallback is called. After all the retry attempts are reached at the max number count, the last call is done to the "recover" method of the RecoveryCallback class.

@Service
public class ApplicationService {

 
@Autowired
 
private FixedBackOffPolicy        fixedBackOffPolicy;

 
@Autowired
 
private ExceptionClassifierRetryPolicy  exceptionClassifierRetryPolicy;

 
@Autowired
 
private WebServiceClientSimulation    webServiceClientSimulation;

 
public String callTheWebService() {

   
final RetryTemplate retryTemplate = new RetryTemplate();

   
// Retry configuration
   
retryTemplate.setBackOffPolicy( fixedBackOffPolicy );
    retryTemplate.setRetryPolicy
( exceptionClassifierRetryPolicy );

   
try {
     
// Call the web service with retry
     
return retryTemplate.execute(
        (
RetryCallback< String, ApplicationException > ) retryContext ->
          webServiceClientSimulation.invoke
(),
        retryContext ->
"Unsuccess. The retry reached maximum retry count :"
         
+ retryContext.getRetryCount() );
   
} catch ( final ApplicationException e ) {
     
e.printStackTrace();

     
return "Exception was been occurred";
   
}
  }

 
public void setFixedBackOffPolicy( final FixedBackOffPolicy fixedBackOffPolicy ) {
   
this.fixedBackOffPolicy = fixedBackOffPolicy;
 
}

 
public void setExceptionClassifierRetryPolicy( final ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy ) {
   
this.exceptionClassifierRetryPolicy = exceptionClassifierRetryPolicy;
 
}

 
public void setWebServiceClientSimulation( final WebServiceClientSimulation webServiceClientSimulation ) {
   
this.webServiceClientSimulation = webServiceClientSimulation;
 
}
}

You can see that I made the inner class implementations with Java8 lambda expressions. Lambda expressions ( Or closures ) are arguably the most exciting and significant new feature with the Java 8 release. The introduction of lambdas enables to use aspects of functional programming, making your code be more concise and flexible. Without lambda expressions, the implementation seems like this:

    try {
     
// Call the web service with retry
     
return retryTemplate.execute( new RetryCallback< String, ApplicationException > () {
         
@Override
         
public String doWithRetry( RetryContext retryContext ) throws ApplicationException {
           
return webServiceClientSimulation.invoke();
         
}     
        }
,
       
new RecoveryCallback< String >() {
         
@Override
         
public String recover( RetryContext retryContext ) throws Exception {
           
return "Unsuccess. The retry reached maximum retry count :"
             
+ retryContext.getRetryCount();
         
}
        })
;
   
} catch ( final ApplicationException e ) {
     
e.printStackTrace();
 
     
return "Exception was been occurred";
   
}   

If you set "yes" as the value of the "simulation.retry" property and "3" as the value of the "retry.count" property, the output of the main method is below:

Web Service was called and executed
Web Service was called and executed
Web Service was called and executed
Unsuccess. The retry reached maximum retry count :3

I hope this gives you some thoughts about Spring Retry. If you have any question, please do not hesitate to contact. My email address is ilker@ilkerkonar.com. All the code that I have demonstrated here is also available in my Github repository: https://github.com/softengilker/draftApplications/tree/master/SpringRetrySample