CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-autoconfigure-retry

Spring Boot auto-configuration for AI retry capabilities with exponential backoff and intelligent HTTP error handling

Overview
Eval results
Files

configuration-properties.mddocs/reference/

Configuration Properties

Comprehensive configuration options for customizing retry behavior in Spring AI applications. All properties use the prefix spring.ai.retry.

Capabilities

SpringAiRetryProperties Configuration

Main configuration class for retry behavior with Spring Boot property binding.

// Package: org.springframework.ai.retry.autoconfigure
/**
 * Configuration properties for AI retry behavior
 * Bind properties with prefix: spring.ai.retry
 * Supports application.properties and application.yml formats
 * Provides configuration metadata for IDE autocomplete
 */
@ConfigurationProperties("spring.ai.retry")
public class SpringAiRetryProperties {

    /**
     * Configuration prefix constant
     * Value: "spring.ai.retry"
     * Use for programmatic property lookups
     */
    public static final String CONFIG_PREFIX = "spring.ai.retry";

    /**
     * Gets the maximum number of retry attempts
     * Default value: 10
     * Minimum recommended: 1
     * Maximum recommended: 100
     * 
     * Behavior:
     * - 0 = no retries (fail immediately on first error)
     * - 1 = one retry after initial failure (2 total attempts)
     * - 10 = ten retries after initial failure (11 total attempts)
     * 
     * Total attempts = maxAttempts value
     * (Note: Some implementations count differently; this counts total attempts)
     * 
     * @return Maximum retry attempts (default: 10)
     */
    public int getMaxAttempts();

    /**
     * Sets the maximum number of retry attempts
     * @param maxAttempts Maximum retry attempts (must be >= 0)
     * @throws IllegalArgumentException if maxAttempts < 0
     */
    public void setMaxAttempts(int maxAttempts);

    /**
     * Gets the backoff configuration for exponential backoff behavior
     * Marked with @NestedConfigurationProperty for Spring Boot configuration metadata
     * Never returns null - always returns a valid Backoff instance
     * Backoff instance is created during properties initialization
     * 
     * @return Backoff configuration object (never null)
     */
    public Backoff getBackoff();

    /**
     * Gets whether to retry on 4xx client errors
     * 
     * Behavior:
     * - false (default): 4xx errors throw NonTransientAiException (no retry)
     * - true: 4xx errors throw TransientAiException (retry)
     * 
     * Exceptions to this setting (take precedence):
     * - Codes in excludeOnHttpCodes always throw NonTransientAiException (no retry)
     * - Codes in onHttpCodes always throw TransientAiException (retry)
     * 
     * 4xx status codes affected:
     * 400 Bad Request, 401 Unauthorized, 402 Payment Required, 403 Forbidden,
     * 404 Not Found, 405 Method Not Allowed, 406 Not Acceptable, 407 Proxy Auth,
     * 408 Timeout, 409 Conflict, 410 Gone, 411-417 (various client errors),
     * 418 I'm a teapot, 421-426 (various client errors), 428-429, 431 Header Fields Too Large,
     * 451 Unavailable For Legal Reasons, and custom 4xx codes
     * 
     * Common use case for true:
     * - Some APIs use 4xx for temporary conditions
     * - 408 Request Timeout might resolve on retry
     * - 429 Too Many Requests (though better in onHttpCodes)
     * 
     * @return true to retry on 4xx, false to treat as non-transient (default: false)
     */
    public boolean isOnClientErrors();

    /**
     * Sets whether to retry on 4xx client errors
     * @param onClientErrors true to retry on 4xx, false to treat as non-transient
     */
    public void setOnClientErrors(boolean onClientErrors);

    /**
     * Gets the list of HTTP status codes that should NOT trigger retry
     * Takes lower precedence than onHttpCodes
     * Takes higher precedence than onClientErrors setting
     * 
     * Common values:
     * - 401 Unauthorized: Invalid credentials, won't resolve on retry
     * - 403 Forbidden: Permission denied, won't resolve on retry
     * - 400 Bad Request: Invalid request format, won't resolve on retry
     * - 404 Not Found: Resource doesn't exist, won't resolve on retry
     * 
     * Default: empty list (no codes explicitly excluded)
     * Never returns null - returns empty list if no codes configured
     * 
     * Example configuration:
     * spring.ai.retry.exclude-on-http-codes=401,403,400,404
     * 
     * @return List of HTTP status codes to treat as non-transient (never null)
     */
    public List<Integer> getExcludeOnHttpCodes();

    /**
     * Sets the list of HTTP status codes that should NOT trigger retry
     * @param excludeOnHttpCodes List of HTTP status codes to treat as non-transient (can be null)
     */
    public void setExcludeOnHttpCodes(List<Integer> excludeOnHttpCodes);

    /**
     * Gets the list of HTTP status codes that should trigger retry
     * Takes highest precedence in error classification
     * Overrides excludeOnHttpCodes and onClientErrors settings
     * 
     * Common values:
     * - 429 Too Many Requests: Rate limit, will resolve after waiting
     * - 503 Service Unavailable: Temporary outage, may resolve quickly
     * - 502 Bad Gateway: Gateway issue, may resolve on retry
     * - 504 Gateway Timeout: Gateway timeout, may succeed on retry
     * 
     * Default: empty list (no codes explicitly included)
     * Never returns null - returns empty list if no codes configured
     * 
     * Example configuration:
     * spring.ai.retry.on-http-codes=429,503,502,504
     * 
     * Note: 5xx codes are typically transient by default
     * This list is useful for:
     * 1. Ensuring specific 5xx codes are retried (if using complex logic)
     * 2. Making specific 4xx codes retryable (e.g., 429)
     * 
     * @return List of HTTP status codes to treat as transient (never null)
     */
    public List<Integer> getOnHttpCodes();

    /**
     * Sets the list of HTTP status codes that should trigger retry
     * @param onHttpCodes List of HTTP status codes to treat as transient (can be null)
     */
    public void setOnHttpCodes(List<Integer> onHttpCodes);
}

Backoff Configuration

Nested static class within SpringAiRetryProperties for exponential backoff parameters.

// Package: org.springframework.ai.retry.autoconfigure
// Nested within: SpringAiRetryProperties
/**
 * Configuration for exponential backoff behavior
 * Nested static class within SpringAiRetryProperties
 * 
 * Backoff formula: wait_time = min(initial_interval × multiplier^(retry_count - 1), max_interval)
 * 
 * Example calculation with defaults (initial=2s, multiplier=5, max=180s):
 * Retry 1: 2 × 5^0 = 2s
 * Retry 2: 2 × 5^1 = 10s
 * Retry 3: 2 × 5^2 = 50s
 * Retry 4: 2 × 5^3 = 250s → capped to 180s
 * Retry 5+: 180s (capped)
 */
public static class Backoff {

    /**
     * Gets the initial sleep duration before first retry attempt
     * 
     * Default: Duration.ofMillis(2000) = 2 seconds
     * Minimum recommended: 100ms (very aggressive)
     * Maximum recommended: 60s (very conservative)
     * 
     * Considerations:
     * - Too low: May overwhelm failing service
     * - Too high: Slow recovery from transient errors
     * - Typical range: 500ms - 5s
     * 
     * For rate-limited APIs: Set higher (5s+) to respect rate limits
     * For internal services: Can be lower (500ms-1s)
     * 
     * Property formats supported:
     * - Milliseconds: spring.ai.retry.backoff.initial-interval=2000ms
     * - Seconds: spring.ai.retry.backoff.initial-interval=2s
     * - ISO-8601: spring.ai.retry.backoff.initial-interval=PT2S
     * 
     * @return Initial backoff duration (default: 2 seconds, never null)
     */
    public Duration getInitialInterval();

    /**
     * Sets the initial sleep duration before first retry attempt
     * @param initialInterval Initial backoff duration (must not be null or negative)
     * @throws IllegalArgumentException if initialInterval is null or negative
     */
    public void setInitialInterval(Duration initialInterval);

    /**
     * Gets the backoff interval multiplier for exponential growth
     * 
     * Default: 5
     * Minimum: 1 (no growth, fixed backoff)
     * Maximum recommended: 10 (very aggressive growth)
     * 
     * Common values:
     * - 1: Fixed backoff (same wait time each retry)
     * - 2: Moderate exponential growth (doubles each time)
     * - 3: Aggressive exponential growth
     * - 5: Very aggressive exponential growth (default)
     * 
     * Growth comparison (initial=1s):
     * multiplier=1: 1s, 1s, 1s, 1s, ...
     * multiplier=2: 1s, 2s, 4s, 8s, 16s, ...
     * multiplier=3: 1s, 3s, 9s, 27s, 81s, ...
     * multiplier=5: 1s, 5s, 25s, 125s, ...
     * 
     * Considerations:
     * - Higher multiplier: Faster reduction in retry frequency
     * - Lower multiplier: More consistent retry frequency
     * - Use 1 for fixed backoff when consistent timing needed
     * 
     * @return Multiplier value (default: 5)
     */
    public int getMultiplier();

    /**
     * Sets the backoff interval multiplier for exponential growth
     * @param multiplier Multiplier value (must be >= 1)
     * @throws IllegalArgumentException if multiplier < 1
     */
    public void setMultiplier(int multiplier);

    /**
     * Gets the maximum backoff duration
     * 
     * Default: Duration.ofMillis(180000) = 3 minutes
     * Minimum recommended: 1s
     * Maximum recommended: 15 minutes (900s)
     * 
     * Purpose: Prevents unbounded exponential growth
     * 
     * Behavior:
     * - Calculated backoff exceeds max → capped to max
     * - All subsequent retries use max interval
     * 
     * Considerations:
     * - Should be less than total timeout requirement
     * - For rate-limited APIs: Set to rate limit window (e.g., 60s)
     * - For transient errors: Higher value allows more recovery time
     * 
     * Total max time = max_interval × remaining_attempts
     * With defaults (max=180s, 10 attempts): ~1800s = 30 minutes total
     * 
     * Property formats supported:
     * - Milliseconds: spring.ai.retry.backoff.max-interval=180000ms
     * - Seconds: spring.ai.retry.backoff.max-interval=180s
     * - Minutes: spring.ai.retry.backoff.max-interval=3m
     * - ISO-8601: spring.ai.retry.backoff.max-interval=PT3M
     * 
     * @return Maximum backoff duration (default: 3 minutes, never null)
     */
    public Duration getMaxInterval();

    /**
     * Sets the maximum backoff duration
     * @param maxInterval Maximum backoff duration (must not be null or negative)
     * @throws IllegalArgumentException if maxInterval is null or negative
     */
    public void setMaxInterval(Duration maxInterval);
}

Configuration Examples

Default Configuration

When no properties are specified, the following defaults are used:

# Default values (no need to specify if these match your requirements)
spring.ai.retry.max-attempts=10
spring.ai.retry.on-client-errors=false
spring.ai.retry.on-http-codes=
spring.ai.retry.exclude-on-http-codes=
spring.ai.retry.backoff.initial-interval=2000ms
spring.ai.retry.backoff.multiplier=5
spring.ai.retry.backoff.max-interval=180000ms

Behavior with defaults:

  • 10 total retry attempts
  • Retries on: 5xx errors, network errors
  • No retry on: 4xx errors
  • Backoff: 2s, 10s, 50s, 180s (capped), 180s, ...
  • Max total time: ~1802s (~30 minutes)

Conservative Retry Configuration

Fewer retries with shorter backoff for faster failure:

spring.ai.retry.max-attempts=3
spring.ai.retry.backoff.initial-interval=500ms
spring.ai.retry.backoff.multiplier=2
spring.ai.retry.backoff.max-interval=5000ms

Behavior:

  • 3 total attempts
  • Backoff: 500ms, 1s, 2s
  • Max total time: ~3.5s
  • Use case: Fast-failing systems, development environments

Aggressive Retry Configuration

More retries with longer backoff for resilience:

spring.ai.retry.max-attempts=15
spring.ai.retry.backoff.initial-interval=3000ms
spring.ai.retry.backoff.multiplier=3
spring.ai.retry.backoff.max-interval=300000ms

Behavior:

  • 15 total attempts
  • Backoff: 3s, 9s, 27s, 81s, 243s (capped to 300s), then 300s for remaining attempts
  • Max total time: ~45 minutes
  • Use case: Critical operations, production systems with high reliability requirements

Rate Limit Handling Configuration

Retry on rate limit errors (429) and service unavailable (503):

spring.ai.retry.max-attempts=10
spring.ai.retry.on-http-codes=429,503
spring.ai.retry.backoff.initial-interval=5000ms
spring.ai.retry.backoff.multiplier=2
spring.ai.retry.backoff.max-interval=60000ms

Behavior:

  • Explicitly retries HTTP 429 (rate limit) and 503 (unavailable)
  • Longer initial interval (5s) to respect rate limits
  • Moderate growth (multiplier=2)
  • Max interval 60s (typical rate limit window)
  • Backoff: 5s, 10s, 20s, 40s, 60s (capped), then 60s
  • Use case: APIs with rate limiting, public AI services

Authentication Error Configuration

Never retry authentication and authorization errors:

spring.ai.retry.max-attempts=10
spring.ai.retry.exclude-on-http-codes=401,403
spring.ai.retry.on-client-errors=false

Behavior:

  • 401 (Unauthorized) and 403 (Forbidden) fail immediately
  • Other 4xx errors also fail immediately (onClientErrors=false)
  • 5xx errors are retried
  • Use case: APIs with authentication, prevent account lockout

Lenient Client Error Configuration

Retry all errors including client errors (use with caution):

spring.ai.retry.max-attempts=5
spring.ai.retry.on-client-errors=true
spring.ai.retry.exclude-on-http-codes=401,403,404

Behavior:

  • Retries most 4xx errors (onClientErrors=true)
  • Except: 401, 403, 404 (explicitly excluded)
  • 400, 405, 422, etc. will be retried
  • Use case: APIs that use 4xx for transient conditions

This configuration retries most 4xx errors but excludes auth errors (401, 403) and not found (404).

Fixed Backoff Configuration

Use fixed backoff instead of exponential:

spring.ai.retry.max-attempts=10
spring.ai.retry.backoff.initial-interval=2000ms
spring.ai.retry.backoff.multiplier=1
spring.ai.retry.backoff.max-interval=2000ms

Behavior:

  • Multiplier=1: No exponential growth
  • All retries wait exactly 2s
  • Predictable, consistent retry timing
  • Use case: When consistent timing is required

YAML Configuration Example

Complete configuration in YAML format:

spring:
  ai:
    retry:
      # Maximum total retry attempts
      max-attempts: 10
      
      # Don't retry 4xx client errors by default
      on-client-errors: false
      
      # HTTP codes that should always be retried
      on-http-codes:
        - 429  # Rate limit (too many requests)
        - 503  # Service unavailable
      
      # HTTP codes that should never be retried
      exclude-on-http-codes:
        - 401  # Unauthorized (invalid credentials)
        - 403  # Forbidden (insufficient permissions)
        - 400  # Bad request (invalid format)
      
      # Exponential backoff configuration
      backoff:
        initial-interval: 2s
        multiplier: 5
        max-interval: 3m

Alternative YAML with ISO-8601 durations:

spring:
  ai:
    retry:
      max-attempts: 10
      backoff:
        initial-interval: PT2S     # ISO-8601: 2 seconds
        multiplier: 5
        max-interval: PT3M         # ISO-8601: 3 minutes

Java Configuration Example

Programmatic configuration using SpringAiRetryProperties:

import org.springframework.ai.retry.autoconfigure.SpringAiRetryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
import java.util.List;
import java.util.Arrays;

@Configuration
public class RetryConfiguration {

    /**
     * Programmatic configuration of retry properties
     * This approach allows dynamic configuration at runtime
     */
    @Bean
    @ConfigurationProperties("spring.ai.retry")
    public SpringAiRetryProperties retryProperties() {
        SpringAiRetryProperties properties = new SpringAiRetryProperties();
        
        // Set max attempts
        properties.setMaxAttempts(10);
        
        // Configure client error handling
        properties.setOnClientErrors(false);
        
        // Set HTTP codes to retry
        properties.setOnHttpCodes(Arrays.asList(429, 503));
        
        // Set HTTP codes to never retry
        properties.setExcludeOnHttpCodes(Arrays.asList(401, 403));
        
        // Configure backoff
        SpringAiRetryProperties.Backoff backoff = properties.getBackoff();
        backoff.setInitialInterval(Duration.ofSeconds(2));
        backoff.setMultiplier(5);
        backoff.setMaxInterval(Duration.ofMinutes(3));
        
        return properties;
    }
}

Conditional configuration based on environment:

import org.springframework.ai.retry.autoconfigure.SpringAiRetryProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import java.time.Duration;

@Configuration
public class EnvironmentSpecificRetryConfig {

    /**
     * Development environment: Fast failure
     */
    @Bean
    @Profile("dev")
    public SpringAiRetryProperties devRetryProperties() {
        SpringAiRetryProperties properties = new SpringAiRetryProperties();
        properties.setMaxAttempts(2);
        properties.getBackoff().setInitialInterval(Duration.ofMillis(100));
        properties.getBackoff().setMultiplier(2);
        properties.getBackoff().setMaxInterval(Duration.ofMillis(500));
        return properties;
    }

    /**
     * Production environment: Robust retry
     */
    @Bean
    @Profile("prod")
    public SpringAiRetryProperties prodRetryProperties() {
        SpringAiRetryProperties properties = new SpringAiRetryProperties();
        properties.setMaxAttempts(10);
        properties.getBackoff().setInitialInterval(Duration.ofSeconds(2));
        properties.getBackoff().setMultiplier(5);
        properties.getBackoff().setMaxInterval(Duration.ofMinutes(3));
        return properties;
    }
}

Property Precedence

HTTP status code handling follows this precedence order (highest to lowest):

  1. onHttpCodes (highest priority): If a status code is in this list, it always throws TransientAiException (retry)
  2. excludeOnHttpCodes: If a status code is in this list (and not in onHttpCodes), it throws NonTransientAiException (no retry)
  3. onClientErrors:
    • If false (default): 4xx errors throw NonTransientAiException (no retry)
    • If true: 4xx errors throw TransientAiException (retry)
  4. Default: All other errors (primarily 5xx) throw TransientAiException (retry)

Example of Precedence

Given this configuration:

spring.ai.retry.on-client-errors=false
spring.ai.retry.on-http-codes=429
spring.ai.retry.exclude-on-http-codes=400

The behavior is:

Status CodeCategoryResultReason
200SuccessNo error-
301RedirectNo error-
400Client errorNo retryIn excludeOnHttpCodes (precedence #2)
401Client errorNo retry4xx + onClientErrors=false (precedence #3)
404Client errorNo retry4xx + onClientErrors=false (precedence #3)
422Client errorNo retry4xx + onClientErrors=false (precedence #3)
429Client errorRetryIn onHttpCodes (precedence #1, highest)
500Server errorRetryDefault behavior (precedence #4)
502Server errorRetryDefault behavior (precedence #4)
503Server errorRetryDefault behavior (precedence #4)
504Server errorRetryDefault behavior (precedence #4)

Complex example with conflicts:

spring.ai.retry.on-client-errors=true
spring.ai.retry.on-http-codes=429,503
spring.ai.retry.exclude-on-http-codes=401,403,429

Behavior for 429:

  • In both onHttpCodes and excludeOnHttpCodes
  • onHttpCodes has higher precedence
  • Result: Retry (TransientAiException)

Backoff Calculation

With exponential backoff, each retry waits progressively longer:

Formula

wait_time = min(initial_interval × multiplier^(retry_count - 1), max_interval)

Where:

  • initial_interval: Starting wait time
  • multiplier: Growth factor
  • retry_count: Current retry number (1-based: 1, 2, 3, ...)
  • max_interval: Maximum cap on wait time

Calculation Examples

Default settings (initial: 2s, multiplier: 5, max: 3min):

Attempt 0: immediate (no wait)
Attempt 1: 2 × 5^(1-1) = 2 × 5^0 = 2 × 1 = 2 seconds
Attempt 2: 2 × 5^(2-1) = 2 × 5^1 = 2 × 5 = 10 seconds
Attempt 3: 2 × 5^(3-1) = 2 × 5^2 = 2 × 25 = 50 seconds
Attempt 4: 2 × 5^(4-1) = 2 × 5^3 = 2 × 125 = 250 seconds → capped to 180 seconds
Attempt 5-10: 180 seconds (capped at max)

Total wait time: 2 + 10 + 50 + 180 + 180 + 180 + 180 + 180 + 180 + 180 = 1,322 seconds ≈ 22 minutes
Total elapsed time (including calls): ~1,322s + (10 × avg_call_time)

Conservative settings (initial: 500ms, multiplier: 2, max: 5s):

Attempt 0: immediate
Attempt 1: 0.5 × 2^0 = 0.5 seconds
Attempt 2: 0.5 × 2^1 = 1 second
Attempt 3: 0.5 × 2^2 = 2 seconds
Attempt 4: 0.5 × 2^3 = 4 seconds
Attempt 5: 0.5 × 2^4 = 8 seconds → capped to 5 seconds
Attempt 6-10: 5 seconds (capped)

Total wait time: 0.5 + 1 + 2 + 4 + 5 + 5 + 5 + 5 + 5 = 32.5 seconds

Fixed backoff (initial: 2s, multiplier: 1, max: 2s):

All attempts: 2 seconds each
Attempt 1: 2 × 1^0 = 2 seconds
Attempt 2: 2 × 1^1 = 2 seconds
Attempt 3: 2 × 1^2 = 2 seconds
...

Total wait time for 10 attempts: 2 × 9 = 18 seconds
(Note: No wait before first attempt, so 9 waits for 10 attempts)

Environment-Specific Configuration

Development Environment

Fast failure for rapid iteration:

# application-dev.properties
spring.ai.retry.max-attempts=2
spring.ai.retry.backoff.initial-interval=100ms
spring.ai.retry.backoff.multiplier=2
spring.ai.retry.backoff.max-interval=500ms

Behavior:

  • Only 2 total attempts (1 retry)
  • Very short waits: 100ms, 200ms
  • Total max time: ~300ms
  • Quick feedback during development

Production Environment

Robust retry for resilience:

# application-prod.properties
spring.ai.retry.max-attempts=10
spring.ai.retry.backoff.initial-interval=2000ms
spring.ai.retry.backoff.multiplier=5
spring.ai.retry.backoff.max-interval=180000ms
spring.ai.retry.on-http-codes=429,503
spring.ai.retry.exclude-on-http-codes=401,403,400

Behavior:

  • 10 total attempts
  • Exponential backoff with long max interval
  • Explicit rate limit handling
  • Max total time: ~30 minutes
  • High resilience to transient failures

Testing Environment

Fast retries for integration tests:

# application-test.properties
spring.ai.retry.max-attempts=3
spring.ai.retry.backoff.initial-interval=50ms
spring.ai.retry.backoff.multiplier=1
spring.ai.retry.backoff.max-interval=50ms

Behavior:

  • 3 attempts total
  • Fixed 50ms backoff (multiplier=1)
  • Total time: ~100ms for all retries
  • Fast test execution

Alternative: Use RetryUtils.SHORT_RETRY_TEMPLATE in tests for fixed 100ms delays without configuration changes.

Staging Environment

Balance between dev and prod:

# application-staging.properties
spring.ai.retry.max-attempts=5
spring.ai.retry.backoff.initial-interval=1000ms
spring.ai.retry.backoff.multiplier=3
spring.ai.retry.backoff.max-interval=30000ms

Behavior:

  • 5 attempts (moderate)
  • Moderate exponential growth
  • Max interval 30s
  • Total max time: ~2 minutes

Configuration Best Practices

1. Set Appropriate Max Attempts

Balance between resilience and timeout requirements:

# Too few: May not recover from transient issues
spring.ai.retry.max-attempts=1  # Only initial call, no retries

# Reasonable: Good for most scenarios
spring.ai.retry.max-attempts=5-10

# Too many: Long wait times, resource waste
spring.ai.retry.max-attempts=100

Consider:

  • API reliability (less reliable = more attempts)
  • User patience (interactive = fewer attempts)
  • Total timeout budget
  • Cost per attempt

2. Configure HTTP Code Lists

Explicitly handle rate limits and auth errors:

# Always retry rate limits (takes precedence)
spring.ai.retry.on-http-codes=429,503

# Never retry auth and validation errors
spring.ai.retry.exclude-on-http-codes=401,403,400,422

Common patterns:

  • Always retry: 429 (rate limit), 503 (unavailable), 502/504 (gateway errors)
  • Never retry: 401 (unauthorized), 403 (forbidden), 400 (bad request), 404 (not found)

3. Adjust Backoff for Your Use Case

For rate-limited external APIs:

spring.ai.retry.backoff.initial-interval=5000ms  # Long initial wait
spring.ai.retry.backoff.multiplier=2            # Moderate growth
spring.ai.retry.backoff.max-interval=60000ms    # Match rate limit window

For internal services:

spring.ai.retry.backoff.initial-interval=500ms   # Short initial wait
spring.ai.retry.backoff.multiplier=3            # Aggressive growth
spring.ai.retry.backoff.max-interval=10000ms    # Lower maximum

For critical operations:

spring.ai.retry.backoff.initial-interval=2000ms  # Reasonable start
spring.ai.retry.backoff.multiplier=5            # Very aggressive growth
spring.ai.retry.backoff.max-interval=300000ms   # Long maximum (5 minutes)

4. Use excludeOnHttpCodes for Permanent Errors

Fail fast on errors that won't resolve:

# These will never succeed on retry
spring.ai.retry.exclude-on-http-codes=401,403,400,404,422

Benefits:

  • Faster failure feedback
  • Reduced load on failing service
  • Lower costs (fewer wasted attempts)

5. Consider Total Timeout

Ensure max_attempts × max_interval aligns with timeout requirements:

# Example: 5 attempts × 10s max = ~50s total timeout
spring.ai.retry.max-attempts=5
spring.ai.retry.backoff.max-interval=10000ms

# If your system timeout is 30s, this configuration will fail
# Reduce attempts or max interval to fit within timeout

Calculate worst-case total time:

Total time ≈ (attempts_after_reaching_max × max_interval) + earlier_backoffs

With defaults:

Total time ≈ (7 attempts × 180s) + (2s + 10s + 50s) ≈ 1,322s ≈ 22 minutes

6. Test Configuration

Verify retry behavior matches expectations:

import org.springframework.ai.retry.autoconfigure.SpringAiRetryProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class RetryConfigurationTest {

    @Autowired
    private SpringAiRetryProperties properties;

    @Test
    void testRetryConfiguration() {
        // Verify properties loaded correctly
        assertThat(properties.getMaxAttempts()).isEqualTo(10);
        assertThat(properties.isOnClientErrors()).isFalse();
        assertThat(properties.getOnHttpCodes()).contains(429, 503);
        assertThat(properties.getExcludeOnHttpCodes()).contains(401, 403);
        
        // Verify backoff configuration
        assertThat(properties.getBackoff().getInitialInterval().toMillis()).isEqualTo(2000);
        assertThat(properties.getBackoff().getMultiplier()).isEqualTo(5);
        assertThat(properties.getBackoff().getMaxInterval().toMinutes()).isEqualTo(3);
    }
}

Integration test for retry behavior:

import org.springframework.retry.support.RetryTemplate;
import org.springframework.ai.retry.TransientAiException;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class RetryBehaviorTest {

    @Test
    void testRetriesOnTransientException() {
        RetryTemplate retryTemplate = createRetryTemplate();
        AtomicInteger attempts = new AtomicInteger(0);

        String result = retryTemplate.execute(context -> {
            attempts.incrementAndGet();
            if (attempts.get() < 3) {
                throw new TransientAiException("Transient error");
            }
            return "success";
        });

        assertThat(result).isEqualTo("success");
        assertThat(attempts.get()).isEqualTo(3);  // Succeeded on 3rd attempt
    }

    @Test
    void testFailsAfterMaxAttempts() {
        RetryTemplate retryTemplate = createRetryTemplate();
        AtomicInteger attempts = new AtomicInteger(0);

        assertThatThrownBy(() -> {
            retryTemplate.execute(context -> {
                attempts.incrementAndGet();
                throw new TransientAiException("Always fails");
            });
        }).isInstanceOf(TransientAiException.class);

        assertThat(attempts.get()).isEqualTo(10);  // Tried max attempts
    }

    private RetryTemplate createRetryTemplate() {
        // Create template with test configuration
        return RetryTemplate.builder()
            .maxAttempts(10)
            .fixedBackoff(10)  // Short delay for tests
            .retryOn(TransientAiException.class)
            .build();
    }
}

7. Monitor and Tune

Add monitoring to understand retry behavior:

import org.springframework.retry.RetryListener;
import org.springframework.retry.RetryContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;

@Configuration
public class RetryMonitoringConfig {

    @Bean
    public RetryListener retryMetricsListener(MeterRegistry meterRegistry) {
        Counter retryCounter = meterRegistry.counter("ai.retry.attempts");
        Counter successCounter = meterRegistry.counter("ai.retry.success");
        Counter failureCounter = meterRegistry.counter("ai.retry.failure");
        Timer retryTimer = meterRegistry.timer("ai.retry.duration");

        return new RetryListener() {
            @Override
            public <T, E extends Throwable> void onError(
                    RetryContext context,
                    RetryCallback<T, E> callback,
                    Throwable throwable) {
                retryCounter.increment();
            }

            @Override
            public <T, E extends Throwable> void close(
                    RetryContext context,
                    RetryCallback<T, E> callback,
                    Throwable throwable) {
                if (throwable == null) {
                    successCounter.increment();
                } else {
                    failureCounter.increment();
                }
            }
        };
    }
}

Analyze metrics to tune configuration:

  • High failure rate → Increase max attempts or max interval
  • Long average duration → Decrease max attempts or backoff
  • Many retries for non-transient errors → Update exclude list
tessl i tessl/maven-org-springframework-ai--spring-ai-autoconfigure-retry@1.1.1

docs

index.md

tile.json