Spring Boot auto-configuration for AI retry capabilities with exponential backoff and intelligent HTTP error handling
Spring AI Retry Auto Configuration provides Spring Boot auto-configuration for AI retry capabilities within the Spring AI framework. It automatically configures a RetryTemplate with exponential backoff for handling transient failures when communicating with AI model providers, and implements intelligent HTTP error handling through a ResponseErrorHandler that distinguishes between transient errors (which should be retried) and non-transient errors (which should fail immediately).
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-retry</artifactId>
<version>1.1.2</version>
</dependency>For Gradle:
implementation 'org.springframework.ai:spring-ai-autoconfigure-retry:1.1.2'The auto-configuration automatically provides beans when the library is on the classpath. To use the beans in your application:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.beans.factory.annotation.Autowired;To access exception types for custom error handling:
import org.springframework.ai.retry.TransientAiException;
import org.springframework.ai.retry.NonTransientAiException;To access utility constants:
import org.springframework.ai.retry.RetryUtils;To use configuration properties:
import org.springframework.ai.retry.autoconfigure.SpringAiRetryProperties;Additional imports for Spring framework components:
import org.springframework.web.client.ResourceAccessException;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.HttpMethod;
import java.net.URI;
import java.io.IOException;For WebFlux support (optional):
import org.springframework.web.reactive.function.client.WebClientRequestException;The auto-configuration automatically creates and configures beans when the library is on the classpath. No explicit configuration is required for default behavior.
When the module is on the classpath, Spring Boot automatically:
RetryTemplate bean with exponential backoff (bean name: "retryTemplate")ResponseErrorHandler bean for HTTP error classification (bean name: "responseErrorHandler")import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;
@Service
public class AiService {
private final RetryTemplate retryTemplate;
public AiService(RetryTemplate retryTemplate) {
this.retryTemplate = retryTemplate;
}
public String callAiModel() {
return retryTemplate.execute(context -> {
// Call to AI model that may throw TransientAiException
// context.getRetryCount() returns the current retry attempt (0-based)
// context.getLastThrowable() returns the last exception thrown
return makeAiApiCall();
});
}
private String makeAiApiCall() {
// Implementation that may throw TransientAiException
return "AI response";
}
}The RetryTemplate.execute() method signature:
/**
* Executes the callback with retry support
* @param callback RetryCallback to execute
* @param <T> Return type
* @param <E> Exception type
* @return Result from callback
* @throws E if all retries are exhausted or non-retryable exception occurs
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> callback) throws E;import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RestTemplateConfig {
private final ResponseErrorHandler responseErrorHandler;
public RestTemplateConfig(ResponseErrorHandler responseErrorHandler) {
this.responseErrorHandler = responseErrorHandler;
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// Set the error handler - it will throw TransientAiException or NonTransientAiException
restTemplate.setErrorHandler(responseErrorHandler);
return restTemplate;
}
}Configure retry behavior in application.properties:
# Maximum number of retry attempts (default: 10)
spring.ai.retry.max-attempts=10
# Retry on 4xx client errors (default: false)
spring.ai.retry.on-client-errors=false
# HTTP codes that should trigger a retry (comma-separated)
spring.ai.retry.on-http-codes=429,503
# HTTP codes that should NOT trigger a retry (comma-separated)
spring.ai.retry.exclude-on-http-codes=401,403
# Backoff configuration
spring.ai.retry.backoff.initial-interval=2000ms
spring.ai.retry.backoff.multiplier=5
spring.ai.retry.backoff.max-interval=180000msOr in application.yml:
spring:
ai:
retry:
max-attempts: 10
on-client-errors: false
on-http-codes:
- 429
- 503
exclude-on-http-codes:
- 401
- 403
backoff:
initial-interval: 2000ms
multiplier: 5
max-interval: 180000msThe module consists of three main layers:
SpringAiRetryAutoConfiguration): Provides Spring Boot auto-configuration that automatically creates and configures beans when the library is detected on the classpathSpringAiRetryProperties): Manages all retry configuration through Spring Boot's property binding system with the prefix spring.ai.retryThe auto-configuration integrates with:
The auto-configuration is registered in:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsorg.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfigurationSpring Boot automatically discovers and applies this auto-configuration when:
RetryUtils.class is available (from spring-ai-retry dependency)@ConditionalOnMissingBean)Automatic configuration of retry beans for Spring AI applications. The auto-configuration conditionally creates beans only when they are not already defined, allowing for custom override.
// Package: org.springframework.ai.retry.autoconfigure
@AutoConfiguration
@ConditionalOnClass(RetryUtils.class)
@EnableConfigurationProperties({ SpringAiRetryProperties.class })
public class SpringAiRetryAutoConfiguration {
/**
* Creates a RetryTemplate bean with exponential backoff
* Bean name: "retryTemplate"
* Condition: Created only if no RetryTemplate bean exists
*
* Configured to retry on:
* - TransientAiException
* - ResourceAccessException (Spring's network error exception)
* - WebClientRequestException (if WebFlux is on classpath)
*
* Uses exponential backoff with parameters from properties:
* - initial interval (default: 2000ms)
* - multiplier (default: 5)
* - max interval (default: 180000ms)
*
* Includes retry listener that logs: "Retry error. Retry count: {count}, Exception: {message}"
*
* @param properties Configuration properties for retry behavior
* @return Configured RetryTemplate instance
*/
@Bean
@ConditionalOnMissingBean
public RetryTemplate retryTemplate(SpringAiRetryProperties properties);
/**
* Creates a ResponseErrorHandler bean for HTTP error classification
* Bean name: "responseErrorHandler"
* Condition: Created only if no ResponseErrorHandler bean exists
*
* Classifies HTTP errors based on configuration:
* 1. Status in onHttpCodes → TransientAiException (retry)
* 2. Status in excludeOnHttpCodes → NonTransientAiException (no retry)
* 3. 4xx when onClientErrors=false → NonTransientAiException (no retry)
* 4. All other errors → TransientAiException (retry)
*
* Error message format: "HTTP {status_code} - {response_body}"
* If response body is empty: "HTTP {status_code} - No response body available"
*
* @param properties Configuration properties for error handling behavior
* @return Configured ResponseErrorHandler instance
*/
@Bean
@ConditionalOnMissingBean
public ResponseErrorHandler responseErrorHandler(SpringAiRetryProperties properties);
}The retryTemplate bean is configured to:
TransientAiException and ResourceAccessExceptionWebClientRequestException when WebFlux is available"Retry error. Retry count: " + context.getRetryCount() + ", Exception: " + throwable.getMessage()The responseErrorHandler bean classifies HTTP errors as:
onHttpCodes, optionally 4xx when onClientErrors=trueonClientErrors=false), configured codes in excludeOnHttpCodesComprehensive configuration options for customizing retry behavior including max attempts, backoff parameters, and HTTP status code handling.
// Package: org.springframework.ai.retry.autoconfigure
@ConfigurationProperties("spring.ai.retry")
public class SpringAiRetryProperties {
/**
* Configuration prefix for all retry properties
* Value: "spring.ai.retry"
*/
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
*
* @return Maximum retry attempts (default: 10)
*/
public int getMaxAttempts();
/**
* Sets the maximum number of retry attempts
* @param maxAttempts Maximum retry attempts (must be >= 0)
*/
public void setMaxAttempts(int maxAttempts);
/**
* Gets the backoff configuration
* Marked with @NestedConfigurationProperty for Spring Boot configuration metadata
* Never returns null - always returns a valid Backoff instance
*
* @return Backoff configuration object
*/
public Backoff getBackoff();
/**
* Gets whether to retry on 4xx client errors
* If false (default): 4xx errors throw NonTransientAiException (no retry)
* If true: 4xx errors throw TransientAiException (retry)
* Exception: Codes in excludeOnHttpCodes always throw NonTransientAiException
* Exception: Codes in onHttpCodes always throw TransientAiException
*
* @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 codes that should NOT trigger retry
* Takes lower precedence than onHttpCodes
* Common values: 401, 403, 400, 404
* Default: empty list
*
* @return List of HTTP status codes to treat as non-transient (never null)
*/
public List<Integer> getExcludeOnHttpCodes();
/**
* Sets the list of HTTP codes that should NOT trigger retry
* @param excludeOnHttpCodes List of HTTP status codes to treat as non-transient
*/
public void setExcludeOnHttpCodes(List<Integer> excludeOnHttpCodes);
/**
* Gets the list of HTTP codes that should trigger retry
* Takes highest precedence in error classification
* Common values: 429 (rate limit), 503 (service unavailable)
* Default: empty list
*
* @return List of HTTP status codes to treat as transient (never null)
*/
public List<Integer> getOnHttpCodes();
/**
* Sets the list of HTTP codes that should trigger retry
* @param onHttpCodes List of HTTP status codes to treat as transient
*/
public void setOnHttpCodes(List<Integer> onHttpCodes);
}Configuration Properties Details
Exception types for classifying errors as transient or non-transient to control retry behavior. Both exception types are from the spring-ai-retry module.
// Package: org.springframework.ai.retry
/**
* Exception for transient AI errors where retry might succeed
* Extends: java.lang.RuntimeException (unchecked exception)
*
* Use cases:
* - Server errors (5xx)
* - Network timeouts
* - Rate limiting (429)
* - Temporary service unavailability (503)
* - Connection errors
*
* Behavior with RetryTemplate:
* - Triggers retry if attempts remain
* - Propagated to caller if all retries exhausted
*
* @since 0.8.1
*/
public class TransientAiException extends RuntimeException {
/**
* Constructs exception with message only
* @param message Error message describing the transient failure
*/
public TransientAiException(String message);
/**
* Constructs exception with message and underlying cause
* @param message Error message describing the transient failure
* @param cause The underlying cause of the exception (can be null)
*/
public TransientAiException(String message, Throwable cause);
}
/**
* Exception for non-transient AI errors where retry will not help
* Extends: java.lang.RuntimeException (unchecked exception)
*
* Use cases:
* - Authentication errors (401)
* - Authorization errors (403)
* - Bad request errors (400)
* - Not found errors (404)
* - Client configuration errors
*
* Behavior with RetryTemplate:
* - Causes immediate failure without retry
* - Propagated to caller immediately
*
* @since 0.8.1
*/
public class NonTransientAiException extends RuntimeException {
/**
* Constructs exception with message only
* @param message Error message describing the non-transient failure
*/
public NonTransientAiException(String message);
/**
* Constructs exception with message and underlying cause
* @param message Error message describing the non-transient failure
* @param cause The underlying cause of the exception (can be null)
*/
public NonTransientAiException(String message, Throwable cause);
}Abstract utility class from the spring-ai-retry module providing default retry templates and error handlers for common use cases.
// Package: org.springframework.ai.retry
/**
* RetryUtils is a utility class for configuring and handling retry operations
* This is an abstract class - cannot be instantiated
* All members are static constants
*
* @since 0.8.1
*/
public abstract class RetryUtils {
/**
* Default retry template with production-ready settings
* Configuration:
* - Max attempts: 10
* - Retries on: TransientAiException, ResourceAccessException
* - Exponential backoff: initial 2s, multiplier 5, max 3min
* - Backoff progression: 2s, 10s, 50s, 180s (capped), 180s, ...
*
* Type: org.springframework.retry.support.RetryTemplate
* Thread-safe: Yes (immutable once created)
*/
public static final RetryTemplate DEFAULT_RETRY_TEMPLATE;
/**
* Retry template for testing with short delays
* Configuration:
* - Max attempts: 10
* - Retries on: TransientAiException, ResourceAccessException
* - Fixed backoff: 100ms (no exponential growth)
* - Total max time: ~1 second for all retries
*
* Use in unit/integration tests to avoid long wait times
*
* Type: org.springframework.retry.support.RetryTemplate
* Thread-safe: Yes (immutable once created)
*/
public static final RetryTemplate SHORT_RETRY_TEMPLATE;
/**
* Default response error handler for HTTP errors
* Behavior:
* - 4xx errors → Throws NonTransientAiException (no retry)
* - 5xx errors → Throws TransientAiException (retry)
* - Error message format: "{status_code} - {response_body}"
* - Reads response body as UTF-8 String
*
* Implements: org.springframework.web.client.ResponseErrorHandler
* Thread-safe: Yes (stateless implementation)
*
* Methods implemented:
* - boolean hasError(ClientHttpResponse response)
* - void handleError(ClientHttpResponse response)
* - void handleError(URI url, HttpMethod method, ClientHttpResponse response)
*/
public static final ResponseErrorHandler DEFAULT_RESPONSE_ERROR_HANDLER;
}ResponseErrorHandler Interface Methods:
The DEFAULT_RESPONSE_ERROR_HANDLER implements the following interface methods:
/**
* Checks if the response has an error status code
* Implementation: Returns response.getStatusCode().isError()
* Returns true for all 4xx and 5xx status codes
*
* @param response The client HTTP response
* @return true if response status code is an error (4xx or 5xx)
* @throws IOException if an I/O error occurs reading the response
*/
boolean hasError(ClientHttpResponse response) throws IOException;
/**
* Handles the error in the given response
* Reads response body and throws appropriate exception:
* - 4xx → NonTransientAiException
* - 5xx → TransientAiException
*
* Error message includes status code and response body text
*
* @param response The client HTTP response with error
* @throws IOException if an I/O error occurs reading the response
* @throws NonTransientAiException for 4xx client errors
* @throws TransientAiException for 5xx server errors
*/
void handleError(ClientHttpResponse response) throws IOException;
/**
* Handles the error in the given response with additional context
* Delegates to handleError(ClientHttpResponse) after logging context
*
* @param url The URL that was called
* @param method The HTTP method that was used (GET, POST, etc.)
* @param response The client HTTP response with error
* @throws IOException if an I/O error occurs reading the response
* @throws NonTransientAiException for 4xx client errors
* @throws TransientAiException for 5xx server errors
*/
void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException;The utility class provides pre-configured constants that can be used directly without Spring Boot auto-configuration.
Nested static class within SpringAiRetryProperties for exponential backoff configuration.
// 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)
*/
public static class Backoff {
/**
* Gets the initial sleep duration before first retry
* Default: Duration.ofMillis(2000) = 2 seconds
* Minimum recommended: 100ms
* Maximum recommended: 60s
*
* @return Initial backoff duration (default: 2 seconds)
*/
public Duration getInitialInterval();
/**
* Sets the initial sleep duration before first retry
* @param initialInterval Initial backoff duration (must not be null or negative)
*/
public void setInitialInterval(Duration initialInterval);
/**
* Gets the backoff interval multiplier for exponential growth
* Default: 5
* Typical values: 2 (moderate growth), 3-5 (aggressive growth), 1 (fixed backoff)
*
* Example with multiplier=5, initial=2s:
* Retry 1: 2s
* Retry 2: 10s (2 × 5)
* Retry 3: 50s (10 × 5)
* Retry 4: 180s (50 × 5, capped at maxInterval)
*
* @return Multiplier value (default: 5)
*/
public int getMultiplier();
/**
* Sets the backoff interval multiplier for exponential growth
* @param multiplier Multiplier value (must be >= 1)
*/
public void setMultiplier(int multiplier);
/**
* Gets the maximum backoff duration
* Default: Duration.ofMillis(180000) = 3 minutes
* Prevents unbounded exponential growth
* Minimum recommended: 1s
* Maximum recommended: 15 minutes
*
* @return Maximum backoff duration (default: 3 minutes)
*/
public Duration getMaxInterval();
/**
* Sets the maximum backoff duration
* @param maxInterval Maximum backoff duration (must not be null or negative)
*/
public void setMaxInterval(Duration maxInterval);
}This module depends on:
org.springframework.ai:spring-ai-retry - Core retry utilities and exception types (required)org.springframework.boot:spring-boot-starter - Spring Boot core functionality (required)Optional dependencies:
Maven dependency tree:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-retry</artifactId>
<version>1.1.2</version>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Transitive: Spring Retry -->
</dependencies>
</dependency>The auto-configured ResponseErrorHandler can be set on any RestTemplate:
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate aiRestTemplate(ResponseErrorHandler errorHandler) {
RestTemplate restTemplate = new RestTemplate();
// Error handler will be invoked on 4xx and 5xx responses
// Before the response is returned to the caller
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}When Spring WebFlux is on the classpath, the auto-configured RetryTemplate automatically includes retry behavior for WebClientRequestException.
WebFlux detection code:
// In SpringAiRetryAutoConfiguration
try {
Class<?> webClientRequestEx = Class.forName(
"org.springframework.web.reactive.function.client.WebClientRequestException"
);
// If class found, add it to retryable exceptions
retryTemplateBuilder.retryOn(webClientRequestEx);
} catch (ClassNotFoundException ignore) {
// WebFlux not on classpath; skip WebClient support
}To provide custom implementations, define your own beans. The auto-configuration will detect them and skip creating its own due to @ConditionalOnMissingBean.
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomRetryConfig {
/**
* Custom RetryTemplate overrides auto-configured bean
* This bean will be used instead of the auto-configured one
*/
@Bean
public RetryTemplate retryTemplate() {
// Custom RetryTemplate configuration
return RetryTemplate.builder()
.maxAttempts(5)
.fixedBackoff(1000)
.build();
}
/**
* Custom ResponseErrorHandler overrides auto-configured bean
* This bean will be used instead of the auto-configured one
*/
@Bean
public ResponseErrorHandler responseErrorHandler() {
// Custom ResponseErrorHandler implementation
return new MyCustomErrorHandler();
}
}The auto-configuration will detect these beans and skip creating its own due to @ConditionalOnMissingBean annotations.
If you need multiple RestTemplate beans with different error handlers:
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;
@Configuration
public class MultipleRestTemplateConfig {
/**
* RestTemplate with auto-configured error handler for AI calls
*/
@Bean
@Qualifier("aiRestTemplate")
public RestTemplate aiRestTemplate(ResponseErrorHandler errorHandler) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
/**
* RestTemplate with default error handler for other API calls
*/
@Bean
@Qualifier("defaultRestTemplate")
public RestTemplate defaultRestTemplate() {
// Uses Spring's default error handler
return new RestTemplate();
}
}Advanced usage with RetryCallback and RecoveryCallback:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryContext;
public class AdvancedRetryUsage {
private final RetryTemplate retryTemplate;
public String callWithRecovery() {
return retryTemplate.execute(
// RetryCallback: main operation
new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
// context.getRetryCount() = current attempt (0-based)
// context.getLastThrowable() = last exception
return performApiCall();
}
},
// RecoveryCallback: fallback if all retries fail
new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
// Called after all retries exhausted
return "Fallback response";
}
}
);
}
private String performApiCall() {
// Implementation
return "result";
}
}Lambda syntax:
public String callWithRecoveryLambda() {
return retryTemplate.execute(
context -> performApiCall(), // RetryCallback
context -> "Fallback response" // RecoveryCallback
);
}tessl i tessl/maven-org-springframework-ai--spring-ai-autoconfigure-retry@1.1.1