In this post, I will show the comparison of the two retries – Spring Retry vs Resilience4j Retry. Usually, you can combine retry with a circuit breaker when implementing to make your application more robust. I already covered the circuit breaker demo. Also, I have updated my book Simplifying Spring Security with Okta Demo if you are interested to learn more about Spring Security.
Spring Retry vs Resilience4j Retry
Spring Retry allows applications to retry a failed operation automatically. In most cases, if your service is calling another service and another service is not responding for some reason, you can use Spring Retry to retry the same operation. This provides another way to make your service more available.
Retry makes your application more robust and less prone to failures. You can either configure Spring Retry on a method that you think can fail or you can configure a RetryTemplate
. The ease of configuration makes Spring Retry an easier choice when writing code.
On other hand, the Resilience4j Retry module offers an equally easier configuration – either through code or through properties.
In this post, I will show how to use Spring Retry and Resilience4j Retry modules when calling any methods or services.
When to use Retry?
Usually, you should consider Retry operation in certain scenarios.
- HTTP call to a REST Endpoint
- Sending or retrieving messages from SQS
- Remote Procedure call or a web service
- Fetching or storing data from databases
In such cases, we can either throw an error if we fail to do the operation successfully. But with the availability of applications becoming more important, most of the time, these errors are trivial and most services come back online within a few milliseconds to seconds.
Therefore, it makes sense to apply retry. You must be careful that the operation that you are applying retry with must be idempotent. Suppose, your application sent a request and the target service received the request, but in between something happened and your target service couldn’t respond in time. Then, with retry, the target service should not treat the retry attempt as a separate or new request. This makes your system more resilient.
Spring Retry
In this section, I will show various ways to use Spring Retry. To start with, we will have a simple Spring Boot REST application to retrieve a list of companies from the database. As usual, I will not show how to build a Spring Boot application.
Gradle Dependencies
To use Spring Retry, we need two dependencies in our configuration.
implementation 'org.springframework.retry:spring-retry:1.3.1'
implementation 'org.springframework:spring-aspects:5.3.5'
EnableRetry Annotation
Once, we have spring-retry
dependency, we will be able to annotate our main class with annotation @EnableRetry
as follows:
package com.betterjavacode.retrydemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
@SpringBootApplication
@EnableRetry
@EnableJpaRepositories(basePackages = "com.betterjavacode.retrydemo.daos")
public class RetrydemoApplication {
public static void main(String[] args) {
SpringApplication.run(RetrydemoApplication.class, args);
}
@Bean
public RetryTemplate retryTemplate()
{
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(100);
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(simpleRetryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
}
I will explain the rest of the code as we go along, but just note here the annotation @EnableRetry
. This will enable the retry in our application.
REST Controller
We will show Spring Retry in two different ways.
- Using
@Retryable
annotation
- Using
RetryTemplate
Our REST Controller will fetch us a list of companies, a company by id, or a list of companies by name. It will look like below:
package com.betterjavacode.retrydemo.controllers;
import com.betterjavacode.retrydemo.dtos.CompanyDto;
import com.betterjavacode.retrydemo.service.CompanyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/v1/betterjavacode/companies")
public class CompanyController
{
@Autowired
CompanyService companyService;
@GetMapping
public ResponseEntity<List> getAllCompanies()
{
List companyDtos = companyService.getAllCompanies();
if(companyDtos.isEmpty())
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(companyDtos, HttpStatus.OK);
}
@GetMapping("/{id}")
public ResponseEntity getCompanyById(@PathVariable("id") long id)
{
CompanyDto companyDto = companyService.getCompany(id);
if(companyDto == null)
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(companyDto, HttpStatus.OK);
}
@GetMapping("/")
public ResponseEntity<List> searchCompanies(@RequestParam("name") String companyName)
{
List companyDtos = companyService.searchCompanyByName(companyName);
if(companyDtos.isEmpty())
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(companyDtos, HttpStatus.OK);
}
}
In our controller, we are using a @Service
object called CompanyService
. This service object provides us with a way to implement our methods to fetch company data.
Service with various Retry Configuration
So, we will see how we can use annotation @Retryable
:
@Retryable(value = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
public List getAllCompanies()
{
List companies = companyRepository.findAll();
List companyDtos = new ArrayList<>();
for(Company company : companies)
{
CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
company.getCity(), company.getState(), company.getDescription());
companyDtos.add(companyDto);
}
return companyDtos;
}
In the above code, we are fetching a list of companies. If this method fails to fetch the result with any exception related to SQLException
, we will retry the fetching. We will retry this twice as configured with maxAttempts
. Between each attempt, there will be a delay of 100 milliseconds. Now if we run our application and call this method, we will see how this retry works.
To simulate the error, I will stop SQL Service from Windows Services. I will show a successful response and a retried response below:
As you can see in the above screenshot, there were two attempts to retry. In each retry, it tried to connect to MySQL server thrice.
What is Spring Boot Retry Template?
Similarly, we can also use retry template that Spring-Retry offers. In the following code, I show a method that I have added in CompanyService
to get company data for an id.
public CompanyDto getCompany(long id)
{
CompanyDto companyDto = retryTemplate.execute(rt -> {
Company company = companyRepository.findById(id).get();
CompanyDto localCompanyDto = new CompanyDto(company.getName(), company.getType(),
company.getCity(),
company.getState(), company.getDescription());
return localCompanyDto;
});
return companyDto;
}
This retryTemplate bean is configured with simpleRetryPolicy
with 2 attempts and 100 milliseconds delay between each attempt. Nevertheless, if I try to execute this method the same way I did for @Retryable
, we will see the below output:
As mentioned above, all I am doing is stopping my MySQL service from windows services and it allows my method to get executed to retry.
Is Retry Template Thread Safe?
Retry Template class is thread-safe. It allows concurrent access. In return, one can execute multiple operations.
Resilience4j Retry
While using resilience4j-retry
library, you can register a custom global RetryConfig
with a RetryRegistry
builder. Use this registry to build a Retry. In our demo to fetch company data, we added a new method to retrieve companies by name.
This method will look like below:
public List searchCompanyByName(String name)
{
LOGGER.info("Search for company = {}", name);
RetryConfig retryConfig =
RetryConfig.custom().maxAttempts(4).waitDuration(Duration.of(2, SECONDS)).build();
RetryRegistry retryRegistry = RetryRegistry.of(retryConfig);
Retry retryConfiguration = retryRegistry.retry("companySearchService", retryConfig);
Supplier<List> companiesSupplier = () -> companyRepository.findAllByName(name);
Supplier<List> retryingCompaniesSearch =
Retry.decorateSupplier(retryConfiguration, companiesSupplier);
List companyDtos = new ArrayList<>();
List companies = retryingCompaniesSearch.get();
LOGGER.info("Retrying..");
for(Company company : companies)
{
CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
company.getCity(), company.getState(), company.getDescription());
companyDtos.add(companyDto);
}
return companyDtos;
}
In the above method, we first create RetryConfig
. We create a RetryRegistry
and add RetryConfig
in this registry. Then when we create our call to fetch a list of companies. We decorate this call with retryConfiguration
.
Customizations with Resilience4j-Retry
RetryConfig
offers different customization:
- maxAttempts – 3 is the default number of attempts for retries.
- waitDuration – a fixed wait duration between each retry attempt.
- intervalFunction – a function to modify the waiting interval after a failure.
- retryOnResultPredicate – configures a predicate that evaluates if a result should be retried.
- retryExceptions – Configures a list of throwable classes that are used for retrying
- ignoreExceptions – Configures a list of throwable classes that are ignored
- failAfterMaxRetries – A boolean to enable or disable throwing of MaxRetriesExceededException when the Retry has reached the configured maxAttempts
Demo
Now, let’s look at what happens when we execute this method with resilience4j-retry
. The following screenshot shows the successful response when SQL service is still running.
If I stop SQL service, we will see the retry attempts 4 times as we have configured it for 4.
Code
The code for this demo can be found in my github repository.
Conclusion
In this post, I showed the comparison between Spring Retry vs Resilience4j Retry. When to use either of these libraries depends on your scenario. Usually, Resilience4j Retry goes well if you also plan to resilience4j circuit breaker module. Spring Retry can be handy with various configurations as well using RetryTemplate
.
If you enjoyed this post, please subscribe to my blog here.