Spring Boot auto-configuration for AI retry capabilities with exponential backoff and intelligent HTTP error handling
Comprehensive configuration options for customizing retry behavior in Spring AI applications. All properties use the prefix spring.ai.retry.
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);
}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);
}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=180000msBehavior with defaults:
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=5000msBehavior:
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=300000msBehavior:
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=60000msBehavior:
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=falseBehavior:
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,404Behavior:
This configuration retries most 4xx errors but excludes auth errors (401, 403) and not found (404).
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=2000msBehavior:
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: 3mAlternative 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 minutesProgrammatic 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;
}
}HTTP status code handling follows this precedence order (highest to lowest):
TransientAiException (retry)NonTransientAiException (no retry)NonTransientAiException (no retry)TransientAiException (retry)TransientAiException (retry)Given this configuration:
spring.ai.retry.on-client-errors=false
spring.ai.retry.on-http-codes=429
spring.ai.retry.exclude-on-http-codes=400The behavior is:
| Status Code | Category | Result | Reason |
|---|---|---|---|
| 200 | Success | No error | - |
| 301 | Redirect | No error | - |
| 400 | Client error | No retry | In excludeOnHttpCodes (precedence #2) |
| 401 | Client error | No retry | 4xx + onClientErrors=false (precedence #3) |
| 404 | Client error | No retry | 4xx + onClientErrors=false (precedence #3) |
| 422 | Client error | No retry | 4xx + onClientErrors=false (precedence #3) |
| 429 | Client error | Retry | In onHttpCodes (precedence #1, highest) |
| 500 | Server error | Retry | Default behavior (precedence #4) |
| 502 | Server error | Retry | Default behavior (precedence #4) |
| 503 | Server error | Retry | Default behavior (precedence #4) |
| 504 | Server error | Retry | Default 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,429Behavior for 429:
With exponential backoff, each retry waits progressively longer:
wait_time = min(initial_interval × multiplier^(retry_count - 1), max_interval)Where:
initial_interval: Starting wait timemultiplier: Growth factorretry_count: Current retry number (1-based: 1, 2, 3, ...)max_interval: Maximum cap on wait timeDefault 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 secondsFixed 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)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=500msBehavior:
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,400Behavior:
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=50msBehavior:
Alternative: Use RetryUtils.SHORT_RETRY_TEMPLATE in tests for fixed 100ms delays without configuration changes.
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=30000msBehavior:
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=100Consider:
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,422Common patterns:
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 windowFor 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 maximumFor 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)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,422Benefits:
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 timeoutCalculate worst-case total time:
Total time ≈ (attempts_after_reaching_max × max_interval) + earlier_backoffsWith defaults:
Total time ≈ (7 attempts × 180s) + (2s + 10s + 50s) ≈ 1,322s ≈ 22 minutesVerify 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();
}
}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:
tessl i tessl/maven-org-springframework-ai--spring-ai-autoconfigure-retry@1.1.1