java 115 lines · 4 tabs

Retrying a Flaky External API with Spring Retry @Retryable and @Recover Fallback

Shared by codesnips Jul 2026
4 tabs
package com.example.rates.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableRetry
public class RetryConfig {

    @Bean
    public RestTemplate rateRestTemplate() {
        return new RestTemplate();
    }
}
4 files · java Explain with highlit

This snippet shows how Spring Retry's declarative annotations wrap a flaky outbound HTTP call so transient failures are retried automatically, with a guaranteed fallback path when the retries are exhausted. The pattern targets the common reality that networks, upstream services, and load balancers occasionally return 5xx, time out, or drop connections — failures that usually succeed on a second attempt but should not crash the calling request.

In RetryConfig, @EnableRetry turns on the aspect that intercepts @Retryable methods. Nothing else is required to activate the feature, but this class exists to make the capability explicit and to allow a shared RetryTemplate bean elsewhere if programmatic retry is ever needed.

ExchangeRateClient is where the policy lives. The @Retryable annotation names the exact exceptions worth retrying — ResourceAccessException for timeouts and connection resets, and a custom TransientUpstreamException — while deliberately excluding others so that programming errors or 4xx client mistakes fail fast instead of being retried pointlessly. maxAttempts = 4 counts the first call plus three retries, and @Backoff adds exponential delay with multiplier = 2.0 and a small random jitter to avoid thundering-herd retries where many clients wake up in lockstep. Inside fetchRate, a 5xx response is translated into TransientUpstreamException so the retry aspect can see a retryable signal rather than a generic status.

The @Recover method recoverRate is the fallback. Spring Retry calls it only after the final attempt fails, and it matches by having a compatible throwable as its first parameter followed by the same argument list as the retried method. Here it returns a cached or last-known rate rather than propagating the failure, keeping the caller resilient. A subtle pitfall the code respects: the @Recover signature must line up with @Retryable or Spring silently rethrows the original exception, so recoverRate mirrors fetchRate exactly.

CurrencyController shows the payoff — it simply calls fetchRate and stays oblivious to the retry machinery. The trade-off is that retries add latency and can amplify load on an already struggling upstream, which is why bounded attempts, backoff, jitter, and a fast fallback all matter together.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
typescript
import React from "react";

type FallbackProps = {
  error: Error;
  reset: () => void;
};

React Error Boundary + error reporting hook

react frontend error-boundary
by codesnips 3 tabs
java
package com.example.demo.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

Messaging with Apache Kafka

java kafka messaging
by David Kumar 3 tabs
ruby
class FeatureFlag < ApplicationRecord
  validates :key, presence: true, uniqueness: true
  validates :percentage, inclusion: { in: 0..100 }

  after_commit :expire_cache

Safer Feature Flagging: Cache + DB Fallback

rails caching reliability
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Retrying a Flaky External API with Spring Retry @Retryable and @Recover Fallback — share card
Link copied