Spring-Retry allows us to retry operations when the codes encounter exceptions. How do we limit the retries to specific exceptions only? For instance, we wouldn’t want to retry SOAP Web Service operations. The operations may throw exceptions due to invalid values or internal errors on the remote services. We would want to retry those operations just because of connectivity issues. We also want to retry operations because the server was busy, etc. This post shows how Spring can retry operations only for specific exceptions.
For Starters
Before we proceed, please go over these posts to better understand this post.
Retry Operation Only for Specific Exception
When we want to retry operations in Spring, we use the RetryTemplate, which we can use in a Java Configuration. Consider the following codes. They create an object of RetryTemplate with some attributes. For example, the backOffPeriod.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); fixedBackOffPolicy.setBackOffPeriod(10000); // 10 seconds retryTemplate.setBackOffPolicy(fixedBackOffPolicy); // Retry for specific exception retryTemplate.setRetryPolicy(new UnableToConnectRemoteServiceExceptionClassifierRetryPolicy()); return retryTemplate; } |
When we create a RetryTemplate object, it is important to set its retryPolicy. In our case, we have a custom retry policy to meet our needs – retry on a specific exception.
The class definition of a Retry Policy looks like the following. In the class, we define the number of maximum attempts and the triggering exception. When there is no exception, the codes use the NeverRetryPolicy, and there will be no retry of operations.
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 | import com.turreta.sampleapp.exception.UnableToConnectRemoteServiceException; import org.springframework.classify.Classifier; import org.springframework.retry.RetryPolicy; import org.springframework.retry.policy.ExceptionClassifierRetryPolicy; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; public class UnableToConnectRemoteServiceExceptionClassifierRetryPolicy extends ExceptionClassifierRetryPolicy { public UnableToConnectRemoteServiceExceptionClassifierRetryPolicy() { final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(); simpleRetryPolicy.setMaxAttempts(3); this.setExceptionClassifier( new Classifier<Throwable, RetryPolicy>() { @Override public RetryPolicy classify( Throwable classifiable ) { // Retry only when UnableToConnectRemoteServiceException was thrown if ( classifiable instanceof UnableToConnectRemoteServiceException) { return simpleRetryPolicy; } // Do not retry for other exceptions return new NeverRetryPolicy(); } } ); } } |
With these, the call to our remote services looks something like the following. We have a public method which is the entry point that has our retry logic implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @Override public MyServiceResponse remoteOperationA(MyServiceRequest myServiceRequest) { RetryCallback<MyServiceResponse> retryCallback = new RetryCallback<MyServiceResponse>() { @Override public MyServiceResponse doWithRetry(RetryContext retryContext) throws Exception { LOGGER.info("[Attempt {}] remoteOperationAInternal", retryContext.getRetryCount() + 1); return MyServiceImpl.this.remoteOperationAInternal(myServiceRequest); } }; MyServiceResponse response = null; try { response = retryTemplate.execute(retryCallback); } catch(Exception e) { e.printStackTrace(); } return response; } |
Within our public method, we call methods that may require trying.
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 | // NOTE: remoteOperationAInternal wraps the actual call to the web method private MyServiceResponse remoteOperationAInternal(MyServiceRequest myServiceRequest) throws UnableToConnectRemoteServiceException { TheirRemoteServiceSOAP port = this.getRemoteService(myServiceRequest); MyServiceResponseImpl response = null; try { response = new MyServiceResponseImpl(port.remoteOperationA(myServiceRequest.getRemoteOperationInputData())); } catch(Exception e) { Throwable throwable = ExceptionUtils.getRootCause(e); if(throwable instanceof ConnectException) { // Will cause retries throw new UnableToConnectRemoteServiceException(); } else { // Will not cause tries throw e; } } return response; } |
Tested on
We tested the codes using the following items. The codes may need changing when using later versions of the Spring Framework and Spring Retry module.
- Spring Framework 3.0.7.RELEASE
- Spring Retry 1.0.3.RELEASE
- Java 8