OK, this is actually not about the winter, which as we all know has already come. It is about Spring Retry, a small Spring Framework library that allows us to add retry functionality to any task that should be retryable.
There’s a very good tutorial here, explaining how the simple retry and recovery is set up. It explains very well how to add a spring-retry dependency, use @Retryable and @Recover annotation and use RetryTemplate with simple policies. What I’d like to linger on is a slightly more complicated case when we actually want to apply different retry behavior based on the type of the exception. This makes sense because we might know that some exceptions are recoverable and some are not, and therefore it doesn’t make too much sense to try and recover from them. For that, there is a specific retry strategy implementation which is called ExceptionClassifierRetryPolicy, which is used with the Spring RetryTemplate.
Let’s suppose we can only recover from IO Exceptions and skip all others. We will create three classes to extend RetryCallback and one class to extend RecoveryCallback to better show what happens inside:
Then we set up our RetryTemplate. We’ll be using a SimpeRetryPolicy with the fixed number of attempts for the IOException and a NeverRetryPolicy which just allows the initial attempt for everything else.
Now we need to use these callbacks to demonstrate how they work. First the successfull execution, which is very simple:
The output for it is as follows:
*** Executing successfull callback... Success callback: attempt 0
Then the Exception:
*** Executing Exception callback... Exception callback: attempt 0 Attempts exhausted. Total: 1 Last exception: Test Exception
And at last our IOException:
*** Executing IO Exception callback... IO Exception callback: attempt 0 IO Exception callback: attempt 1 IO Exception callback: attempt 2 Attempts exhausted. Total: 3 Last exception: Test IO Exception
As we can see, only IOException initiated three attempts. Note that the attempts are numbered from 0 because when the callback is executed the attempt is not exhausted, so the last attempt has #2 and not #3. But on RecoveryCallback all the attempts are exhausted, so the context holds 3 attempts.
We can also see that the RecoveryCallback isn’t called when the attempts were a success. That is, it is only called when the execution ended with an exception.
The RetryTemplate is synchronous, so all the execution happens in our main thread. That is why I added try/catch blocks around the calls, to allow the program run all three examples without a problem. Otherwise the retry policy would rethrow the exception after its last unsuccessful attempt and would stop the execution.
There is also a very interesting CompositeRetryPolicy which allows to add several policies and delegates to call them in order, one by one. It can also allow to create quite a flexible retry strategy, but that is another topic in itself.
I think that spring-retry is a very useful library which allows to make common retryable tasks more predictable, testable and easier to implement.