DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Mastering System Design: A Comprehensive Guide to System Scaling for Millions (Part 1)
  • Supercharging Productivity in Microservice Development With AI Tools
  • A Guide to Enhanced Debugging and Record-Keeping

Trending

  • How to Submit a Post to DZone
  • The Smart Way to Talk to Your Database: Why Hybrid API + NL2SQL Wins
  • Vibe Coding With GitHub Copilot: Optimizing API Performance in Fintech Microservices
  • How To Build Resilient Microservices Using Circuit Breakers and Retries: A Developer’s Guide To Surviving
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Ensuring API Resilience in Spring Microservices Using Retry and Fallback Mechanisms

Ensuring API Resilience in Spring Microservices Using Retry and Fallback Mechanisms

Implement retry mechanisms and fallback methods in Spring microservices to handle API failures, ensuring enhanced reliability and improved user experience.

By 
Amol Gote user avatar
Amol Gote
DZone Core CORE ·
Nov. 16, 23 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
7.3K Views

Join the DZone community and get the full member experience.

Join For Free

In the digital landscape of today, applications heavily rely on external HTTP/REST APIs for a wide range of functionalities. These APIs often orchestrate a complex web of internal and external API calls. This creates a network of dependencies. Therefore, when a dependent API fails or undergoes downtime, the primary application-facing API needs adeptly handle these disruptions gracefully. In light of this, this article explores the implementation of retry mechanisms and fallback methods in Spring microservices. Specifically, it highlights how these strategies can significantly bolster API integration reliability and notably improve user experience.

Understanding Dependent API Failures

Mobile and web applications consuming APIs dependent on other services for successful execution face unique challenges. For instance, calls to dependent APIs can fail for a variety of reasons, including network issues, timeouts, internal server errors, or scheduled downtimes. As a result, such failures can compromise user experience, disrupt crucial functionalities, and lead to data inconsistencies. Thus, implementing strategies to gracefully handle these failures is vital for maintaining system integrity.

Retry Mechanisms

As a primary solution, retry mechanisms serve to handle transient errors and temporary issues. By automatically reattempting an API call, this mechanism can often resolve problems related to brief network glitches or temporary server unavailability. Importantly, it's crucial to differentiate between scenarios suitable for retries, such as network timeouts, and those where retries might be ineffective or even detrimental, like business logic exceptions or data validation errors.

Retry Strategies

Common approaches include:

  • Fixed interval retries: Attempting retries at regular intervals
  • Exponential backoff: This strategy involves increasing the interval between retries exponentially, thereby reducing the load on the server and network.

Moreover,  both methods should be accompanied by a maximum retry limit to prevent infinite loops. Additionally, it’s essential to monitor and log each retry attempt for future analysis and system optimization.

Retry and Fallback in Spring Microservices 

There are 2 ways in which we can implement the Retry and Fallback method.

1. resilience4j

@Retry annotation is a declarative way designed to simplify the implementation of retry logic in applications. This annotation is available in the resilience4j package.  By applying this annotation to service methods, Spring handles the retry process automatically when specified types of exceptions are encountered. 

The following is a real implementation example. The method calls an API to pull the bureau report for underwriting a loan application. If this method fails, the entire loan application underwriting process fails, impacting the consuming application, such as a mobile application. So we have annotated this method with @Retry:

Java
 
@Override
@Retry(name = "SOFT_PULL", fallbackMethod = "performSoftPull_Fallback")
public CreditBureauReportResponse performSoftPull(SoftPullParams softPullParams, ErrorsI error) {
    CreditBureauReportResponse result = null;
    try {
        Date dt = new Date();
        logger.info("UnderwritingServiceImpl::performSoftPull method call at :" + dt.toString() + ", for loan acct id:" + softPullParams.getUserLoanAccountId());
        CreditBureauReportRequest request = this.getCreditBureauReportRequest(softPullParams);
        RestTemplate restTemplate = this.externalApiRestTemplateFactory.getRestTemplate("SOFT_PULL", error);
        HttpHeaders headers = this.getHttpHeaders(softPullParams);
        HttpEntity<CreditBureauReportRequest> entity = new HttpEntity<>(request, headers);
        long startTime = System.currentTimeMillis();
        String uwServiceEndPoint = "/transaction";
        String callUrl = String.format("%s%s", appConfig.getUnderwritingTransactionApiPrefix(), uwServiceEndPoint);
        ResponseEntity<CreditBureauReportResponse> responseEntity = restTemplate.exchange(callUrl, HttpMethod.POST, entity, CreditBureauReportResponse.class);
        result = responseEntity.getBody();
        long endTime = System.currentTimeMillis();
        long timeDifference = endTime - startTime;
        logger.info("Time taken for API call SOFT_PULL/performSoftPull call 1: " + timeDifference);
    } catch (HttpClientErrorException exception) {
        logger.error("HttpClientErrorException occurred while calling SOFT_PULL API, response string: " + exception.getResponseBodyAsString());
        throw exception;
    } catch (HttpStatusCodeException exception) {
        logger.error("HttpStatusCodeException occurred while calling SOFT_PULL API, response string: " + exception.getResponseBodyAsString());
        throw exception;
    } catch (Exception ex) {
        logger.error("Error occurred in performSoftPull. Detail error:", ex);
        throw ex;
    }
    return result;
}


We can define the other attributes like the number of retries and delays between retries in the application.yml file:

YAML
 
resilience4j.retry:
  configs:
    default:
      maxRetryAttempts: 3
      waitDuration: 100
    externalPartner:
      maxRetryAttempts: 2
      waitDuration: 1000
  instances:
    SOFT_PULL:
      baseConfig: externalPartner


We specify the fallback method fallbackMethod = "performSoftPull_Fallback". This method is invoked if all the configured retry attempts fail; in this case, two.

Java
 
public CreditBureauReportResponse performSoftPull_Fallback(SoftPullParams softPullParams, ErrorsI error, Exception extPartnerException) {
    logger.info("UnderwritingServiceImpl::performSoftPull_Fallback - fallback , method called for soft pull api call");
    CreditBureauReportResponse creditBureauReportResponse = null;
    String loanAcctId = softPullParams.getUserLoanAccountId();
    ApplicantCoBorrowerIdsMapping applicantCoBorrowerIdsMapping = this.uwCoBorrowerRepository.getApplicantCoBorrowerIdsMapping(loanAcctId);
    try {
        boolean result = this.partnerServiceExceptionRepository.savePartnerServiceException(applicantCoBorrowerIdsMapping.getApplicantUserId(),
                applicantCoBorrowerIdsMapping.getLoanId(), PartnerService.SOFT_PULL.getValue(), "GDS", null);

        if (!result) {
            logger.error("UnderwritingServiceImpl::performSoftPull_Fallback - Unable to save entry in the partner service exception table.");
        }
        LoanSubStatus loanSubStatus = LoanSubStatus.PARTNER_API_ERROR;
        result = this.loanUwRepository.saveLoanStatus(applicantCoBorrowerIdsMapping.getApplicantUserId(), applicantCoBorrowerIdsMapping.getLoanId(),
                IcwLoanStatus.INITIATED.getValue(), loanSubStatus.getName(), "Partner Service Down", null);
        if (!result) {
            logger.error("UnderwritingServiceImpl::performSoftPull_Fallback - Unable to update loan status, sub status when partner service is down.");
        }
    } catch (Exception ex) {
        logger.error("UnderwritingServiceImpl::performSoftPull_Fallback - An error occurred while calling softPullExtPartnerFallbackService, detail error:", ex);
    }
    creditBureauReportResponse = new CreditBureauReportResponse();
    UnderwritingApiError underwritingApiError = new UnderwritingApiError();
    underwritingApiError.setCode("IC-EXT-PARTNER-1001");
    underwritingApiError.setDescription("Soft Pull API error");
    List<UnderwritingApiError> underwritingApiErrors = new ArrayList<>();
    underwritingApiErrors.add(underwritingApiError);
    creditBureauReportResponse.setErrors(underwritingApiErrors);
    return creditBureauReportResponse;
}


In this scenario, the fallback method returns the same response object as the original method. However, we also record in our data storage that the service is down and save state, and relay an indicator back to the consumer service method. This indicator is then passed on to the consuming mobile application, alerting the user about issues with our partner services. Once the issue is rectified, we utilize the persisted state to resume the workflow and send a notification to the mobile application, indicating that normal operations can continue.

2. spring-retry

In this case, we need to install the spring-retry and spring-aspects packages. For the same method as above, we will replace it with @Retry annotation:

Java
 
@Retryable(retryFor = {HttpClientErrorException.class, HttpStatusCodeException.class, Exception.class}, maxAttempts = 2, backoff = @Backoff(delay = 100))
public CreditBureauReportResponse performSoftPull(SoftPullParams softPullParams, ErrorsI error) {


The @Retryable annotation in Spring allows us to specify multiple exception types that should trigger a retry. We can list these exception types in the value attribute of the annotation.

To write a fallback method for our @Retryable annotated method performSoftPull, we would use the @Recover annotation. This method is invoked when the performSoftPull method exhausts its retry attempts due to the specified exceptions (HttpClientErrorException, HttpStatusCodeException, Exception). The @Recover method should have a matching signature to the @Retryable method, with the addition of the exception type as the first parameter.

Java
 
@Recover
public CreditBureauReportResponse fallbackForPerformSoftPull(HttpClientErrorException ex, SoftPullParams softPullParams, ErrorsI error) {
  // Fallback Implementation
}

@Recover
public CreditBureauReportResponse fallbackForPerformSoftPull(HttpStatusCodeException ex, SoftPullParams softPullParams, ErrorsI error) {
  // Fallback Implementation
}

@Recover
public CreditBureauReportResponse fallbackForPerformSoftPull(Exception ex, SoftPullParams softPullParams, ErrorsI error) {
  // Fallback Implementation
}


Conclusion

In summary, in Spring microservices, effectively handling API failures with retry mechanisms and fallback methods is essential for building robust, user-centric applications. These strategies ensure the application remains functional and provides a seamless user experience, even in the face of API failures. By implementing retries for transient issues and defining fallback methods for more persistent failures, Spring applications can offer reliability and resilience in today’s interconnected digital world.

API Exponential backoff REST Spring Framework microservice

Opinions expressed by DZone contributors are their own.

Related

  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Mastering System Design: A Comprehensive Guide to System Scaling for Millions (Part 1)
  • Supercharging Productivity in Microservice Development With AI Tools
  • A Guide to Enhanced Debugging and Record-Keeping

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!