Spring Boot auto-configuration for AI retry capabilities with exponential backoff and intelligent HTTP error handling
Spring Boot auto-configuration for AI retry capabilities. The auto-configuration automatically creates and configures retry beans when the library is detected on the classpath.
Main auto-configuration class that provides retry beans.
// Package: org.springframework.ai.retry.autoconfigure
/**
* Auto-configuration for AI Retry
* Provides beans for retry template and response error handling
* Handles transient and non-transient exceptions based on HTTP status codes
*
* Activated when RetryUtils is on the classpath
* Automatically imports SpringAiRetryProperties for configuration
*
* Auto-configuration order: After DataSourceAutoConfiguration, Before WebMvcAutoConfiguration
* Configuration properties prefix: "spring.ai.retry"
*/
@AutoConfiguration
@ConditionalOnClass(RetryUtils.class)
@EnableConfigurationProperties({ SpringAiRetryProperties.class })
public class SpringAiRetryAutoConfiguration {
/**
* Creates a RetryTemplate bean with exponential backoff
* Bean name: "retryTemplate"
* Only created if no RetryTemplate bean already exists
*
* Configured to:
* - Retry on TransientAiException
* - Retry on ResourceAccessException (Spring's I/O exception for network errors)
* - Retry on WebClientRequestException (if WebFlux available, detected at runtime)
* - Use exponential backoff with configurable parameters
* - Log warnings on each retry attempt
*
* Retry listener logs format:
* "Retry error. Retry count: " + context.getRetryCount() + ", Exception: " + throwable.getMessage()
*
* Maximum total time (worst case):
* With defaults (max-attempts=10, initial=2s, multiplier=5, max=180s):
* ~1802 seconds = ~30 minutes total for all retries
*
* Backoff progression with defaults:
* Attempt 0: no wait (initial call)
* Attempt 1: 2s wait
* Attempt 2: 10s wait (2 × 5)
* Attempt 3: 50s wait (10 × 5)
* Attempt 4: 180s wait (50 × 5, capped at max)
* Attempts 5-9: 180s wait each (capped at max)
*
* @param properties Configuration properties for retry behavior
* @return Configured RetryTemplate instance (never null)
*/
@Bean
@ConditionalOnMissingBean
public RetryTemplate retryTemplate(SpringAiRetryProperties properties);
/**
* Creates a ResponseErrorHandler bean for HTTP error classification
* Bean name: "responseErrorHandler"
* Only created if no ResponseErrorHandler bean already exists
*
* Returns an implementation of ResponseErrorHandler with the following methods:
* - boolean hasError(ClientHttpResponse response): Checks if response has error status
* - void handleError(ClientHttpResponse response): Handles error based on status code
* - void handleError(URI, HttpMethod, ClientHttpResponse): Handles error with context
*
* Classifies HTTP errors based on status codes and configuration (in precedence order):
* 1. HTTP codes in onHttpCodes list → TransientAiException (retry)
* 2. HTTP codes in excludeOnHttpCodes list → NonTransientAiException (no retry)
* 3. 4xx when onClientErrors=false (default) → NonTransientAiException (no retry)
* 4. 4xx when onClientErrors=true → TransientAiException (retry)
* 5. All other errors (5xx, network, etc.) → TransientAiException (retry)
*
* Error message format: "HTTP {status_code} - {response_body}"
* If response body is null/empty: "HTTP {status_code} - No response body available"
* Response body is read as UTF-8 String, limited to 4KB to prevent memory issues
*
* @param properties Configuration properties for error handling behavior
* @return Configured ResponseErrorHandler instance (never null)
*/
@Bean
@ConditionalOnMissingBean
public ResponseErrorHandler responseErrorHandler(SpringAiRetryProperties properties);
}The auto-configured RetryTemplate provides the following behavior:
The template retries on the following exception types:
TransientAiException: Explicitly indicates a transient error (from spring-ai-retry)
ResourceAccessException: Network/connection errors from Spring's RestTemplate
WebClientRequestException: Network/connection errors from Spring WebFlux WebClient (if WebFlux is on classpath)
Backoff behavior is configured from properties:
Initial interval: Time before first retry (default: 2 seconds)
spring.ai.retry.backoff.initial-intervalMultiplier: Growth factor for each retry (default: 5)
spring.ai.retry.backoff.multiplierMax interval: Maximum wait time (default: 3 minutes)
spring.ai.retry.backoff.max-intervalFormula: wait_time = min(initial_interval × multiplier^(retry_count - 1), max_interval)
Default settings (initial=2s, multiplier=5, max=180s):
Attempt 0: immediate (no wait)
Attempt 1: wait 2s (2 × 5^0 = 2)
Attempt 2: wait 10s (2 × 5^1 = 10)
Attempt 3: wait 50s (2 × 5^2 = 50)
Attempt 4: wait 180s (2 × 5^3 = 250, capped at 180)
Attempt 5: wait 180s (capped)
...
Attempt 9: wait 180s (capped)
Total time: ~1802s = ~30 minutesConservative settings (initial=500ms, multiplier=2, max=5s):
Attempt 0: immediate
Attempt 1: wait 500ms (0.5 × 2^0 = 0.5)
Attempt 2: wait 1s (0.5 × 2^1 = 1)
Attempt 3: wait 2s (0.5 × 2^2 = 2)
Attempt 4: wait 4s (0.5 × 2^3 = 4)
Attempt 5: wait 5s (0.5 × 2^4 = 8, capped at 5)
...
Total time for 10 attempts: ~42.5sFixed backoff (initial=1s, multiplier=1, max=1s):
All retries: wait 1s each
Total time for 10 attempts: ~9sThe template includes a listener that logs warnings on each retry:
Log format:
Retry error. Retry count: {count}, Exception: {message}Examples:
Retry error. Retry count: 1, Exception: HTTP 503 - Service temporarily unavailable
Retry error. Retry count: 2, Exception: HTTP 429 - Rate limit exceeded
Retry error. Retry count: 3, Exception: Connection timeoutThe listener provides visibility into retry behavior during operation without requiring additional configuration.
Logger used: org.springframework.retry.support.RetryTemplate Log level: WARN
The configured RetryTemplate provides these key methods:
/**
* Execute the callback with retry support
* Retries on configured exceptions until max attempts reached
*
* @param callback RetryCallback to execute
* @param <T> Return type of callback
* @param <E> Exception type that can be thrown
* @return Result from successful callback execution
* @throws E If all retries exhausted or non-retryable exception thrown
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> callback) throws E;
/**
* Execute the callback with retry and recovery support
* If all retries fail, calls recovery callback instead of throwing exception
*
* @param retryCallback RetryCallback to execute
* @param recoveryCallback RecoveryCallback for fallback behavior
* @param <T> Return type
* @param <E> Exception type
* @return Result from retry callback or recovery callback
* @throws E If exception occurs during recovery
*/
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback) throws E;The auto-configuration attempts to load WebClientRequestException at runtime:
// In retryTemplate() method
try {
Class<?> webClientRequestEx = Class.forName(
"org.springframework.web.reactive.function.client.WebClientRequestException"
);
// If class found, add to retryable exceptions
retryTemplateBuilder.retryOn(webClientRequestEx);
} catch (ClassNotFoundException ignore) {
// WebFlux not on classpath; skip WebClient support
// No error or warning logged - this is expected behavior
}This allows the same auto-configuration to work with or without WebFlux on the classpath.
Result:
The auto-configured ResponseErrorHandler classifies HTTP errors for retry decisions.
The bean returns an anonymous implementation of the ResponseErrorHandler interface with the following methods:
/**
* Checks if the HTTP response has an error status code
* Implementation: return response.getStatusCode().isError();
*
* @param response The client HTTP response to check
* @return true if status code is 4xx or 5xx (error), false for 2xx/3xx
* @throws IOException if an I/O error occurs reading the status
*/
boolean hasError(ClientHttpResponse response) throws IOException;
/**
* Handles the error in the HTTP response
* Reads response body and throws appropriate exception based on status code
*
* Response body handling:
* - Read as UTF-8 String
* - Limited to 4KB to prevent memory issues with large error responses
* - If empty/null, uses placeholder: "No response body available"
*
* Error message format: "HTTP {status_code} - {response_body}"
* Examples:
* - "HTTP 429 - Rate limit exceeded. Retry after 60 seconds."
* - "HTTP 401 - Invalid API key provided."
* - "HTTP 503 - No response body available"
*
* @param response The client HTTP response with error status
* @throws IOException if an I/O error occurs reading the response body
* @throws TransientAiException for retryable errors (5xx, configured codes)
* @throws NonTransientAiException for non-retryable errors (4xx, excluded codes)
*/
void handleError(ClientHttpResponse response) throws IOException;
/**
* Handles the error with additional request context
* Delegates to handleError(ClientHttpResponse) after optional logging
*
* @param url The URL that was requested
* @param method The HTTP method used (GET, POST, PUT, DELETE, etc.)
* @param response The client HTTP response with error status
* @throws IOException if an I/O error occurs
* @throws TransientAiException for retryable errors
* @throws NonTransientAiException for non-retryable errors
*/
void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException;The handler follows this decision flow (in precedence order):
1. Check if response has error status code (4xx or 5xx)
↓ No → Return (no error)
↓ Yes
2. Extract error message from response body (UTF-8, max 4KB)
↓
3. Is status code in properties.getOnHttpCodes()?
↓ Yes → Throw TransientAiException (retry) ✓
↓ No
4. Is status code in properties.getExcludeOnHttpCodes()?
↓ Yes → Throw NonTransientAiException (no retry) ✗
↓ No
5. Is status code 4xx (400-499)?
↓ Yes
├─ Is properties.isOnClientErrors() true?
│ ↓ Yes → Throw TransientAiException (retry) ✓
│ ↓ No → Throw NonTransientAiException (no retry) ✗
↓ No (5xx or other)
6. Default → Throw TransientAiException (retry) ✓Error messages include the HTTP status code and response body:
Format: HTTP {status_code} - {response_body}
Examples:
HTTP 429 - Rate limit exceeded. Please retry after 60 seconds.
HTTP 401 - Invalid authentication credentials.
HTTP 503 - Service temporarily unavailable due to maintenance.
HTTP 500 - Internal server error: NullPointerException at line 42
HTTP 502 - Bad Gateway
HTTP 504 - Gateway TimeoutIf the response body is empty or null:
HTTP 503 - No response body available
HTTP 500 - No response body availableResponse body reading:
The hasError method checks if the response status code is an error:
// Implementation in responseErrorHandler bean
public boolean hasError(ClientHttpResponse response) throws IOException {
HttpStatusCode statusCode = response.getStatusCode();
// isError() returns true for 4xx and 5xx codes
return statusCode.isError();
}Returns true for:
Returns false for:
The handleError method reads the response body and throws the appropriate exception:
// Pseudocode for handleError implementation
public void handleError(ClientHttpResponse response) throws IOException {
int statusCode = response.getStatusCode().value();
// Read response body (UTF-8, max 4KB)
String responseBody = readResponseBody(response);
if (responseBody == null || responseBody.isEmpty()) {
responseBody = "No response body available";
}
String errorMessage = "HTTP " + statusCode + " - " + responseBody;
// Classification logic
if (properties.getOnHttpCodes().contains(statusCode)) {
throw new TransientAiException(errorMessage);
}
if (properties.getExcludeOnHttpCodes().contains(statusCode)) {
throw new NonTransientAiException(errorMessage);
}
if (statusCode >= 400 && statusCode < 500) {
if (properties.isOnClientErrors()) {
throw new TransientAiException(errorMessage);
} else {
throw new NonTransientAiException(errorMessage);
}
}
// Default: all other errors are transient (primarily 5xx)
throw new TransientAiException(errorMessage);
}Example configuration:
spring.ai.retry.on-client-errors=false
spring.ai.retry.on-http-codes=429,503
spring.ai.retry.exclude-on-http-codes=401,403,400Classification results:
Simply add the dependency and the beans are auto-configured:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.stereotype.Service;
@Service
public class AiClientService {
private final RetryTemplate retryTemplate;
private final ResponseErrorHandler errorHandler;
// Constructor injection - Spring auto-wires the auto-configured beans
public AiClientService(
RetryTemplate retryTemplate,
ResponseErrorHandler errorHandler) {
this.retryTemplate = retryTemplate;
this.errorHandler = errorHandler;
}
public String callAiApi() {
return retryTemplate.execute(context -> {
// context is RetryContext with methods:
// - getRetryCount(): int (0 for first attempt)
// - getLastThrowable(): Throwable (null on first attempt)
// - getAttribute(name): Object (custom attributes)
return performApiCall();
});
}
private String performApiCall() {
// Implementation that may throw TransientAiException
return "result";
}
}Configure RestTemplate with the auto-configured error handler:
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 {
/**
* Creates RestTemplate with auto-configured error handler
* Error handler will:
* 1. Be invoked for all 4xx and 5xx responses
* 2. Throw TransientAiException or NonTransientAiException
* 3. Allow RetryTemplate to handle retry logic
*/
@Bean
public RestTemplate aiRestTemplate(ResponseErrorHandler errorHandler) {
RestTemplate restTemplate = new RestTemplate();
// Set error handler - invoked before response returned to caller
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}Use both beans together for comprehensive retry handling:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;
import org.springframework.stereotype.Service;
@Service
public class AiService {
private final RetryTemplate retryTemplate;
private final RestTemplate restTemplate;
public AiService(RetryTemplate retryTemplate, RestTemplate aiRestTemplate) {
this.retryTemplate = retryTemplate;
this.restTemplate = aiRestTemplate; // Has auto-configured error handler
}
public String getAiCompletion(String prompt) {
return retryTemplate.execute(context -> {
// Flow:
// 1. RestTemplate makes HTTP request
// 2. If response is 4xx/5xx, ResponseErrorHandler is invoked
// 3. ResponseErrorHandler throws TransientAiException or NonTransientAiException
// 4. RetryTemplate catches TransientAiException and retries
// 5. RetryTemplate propagates NonTransientAiException immediately
return restTemplate.postForObject(
"https://api.example.com/complete",
prompt,
String.class
);
});
}
}When Spring WebFlux is on the classpath, the RetryTemplate automatically handles WebClient exceptions:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class ReactiveAiService {
private final RetryTemplate retryTemplate;
private final WebClient webClient;
public ReactiveAiService(RetryTemplate retryTemplate, WebClient.Builder builder) {
this.retryTemplate = retryTemplate;
this.webClient = builder.baseUrl("https://api.example.com").build();
}
/**
* Uses RetryTemplate with reactive WebClient
* Note: block() makes the call synchronous, compatible with RetryTemplate
* For fully reactive retry, use reactor-retry instead
*/
public String getAiCompletion(String prompt) {
return retryTemplate.execute(context -> {
// WebClientRequestException thrown on network errors
// RetryTemplate catches and retries (when WebFlux detected)
return webClient.post()
.uri("/complete")
.bodyValue(prompt)
.retrieve()
.bodyToMono(String.class)
.block(); // Synchronous blocking call
});
}
}Access retry context for advanced scenarios:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
public class AdvancedRetryService {
private final RetryTemplate retryTemplate;
public String callWithContext() {
return retryTemplate.execute(new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
// Get retry count (0-based)
int retryCount = context.getRetryCount();
// Get last exception (null on first attempt)
Throwable lastException = context.getLastThrowable();
// Set/get custom attributes
context.setAttribute("customKey", "customValue");
Object value = context.getAttribute("customKey");
// Log retry attempt
System.out.println("Attempt " + (retryCount + 1));
return performApiCall();
}
});
}
private String performApiCall() {
return "result";
}
}Provide fallback behavior when all retries fail:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryContext;
public class RetryWithFallbackService {
private final RetryTemplate retryTemplate;
public String callWithFallback() {
return retryTemplate.execute(
// Retry callback
context -> performApiCall(),
// Recovery callback - invoked when all retries exhausted
new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
// context.getRetryCount() = max attempts
// context.getLastThrowable() = last exception thrown
Throwable lastError = context.getLastThrowable();
System.err.println("All retries failed: " + lastError.getMessage());
// Return fallback value instead of throwing exception
return "Fallback response - service temporarily unavailable";
}
}
);
}
private String performApiCall() {
return "result";
}
}Provide your own RetryTemplate bean to override the auto-configuration:
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomRetryConfig {
/**
* Custom RetryTemplate bean
* Overrides auto-configured bean due to @ConditionalOnMissingBean
*/
@Bean
public RetryTemplate retryTemplate() {
// Using builder API
return RetryTemplate.builder()
.maxAttempts(5)
.fixedBackoff(1000) // 1 second fixed backoff
.retryOn(Exception.class) // Retry on any exception
.build();
}
/**
* Alternative: Manual configuration with policies
*/
@Bean
public RetryTemplate retryTemplateManual() {
RetryTemplate template = new RetryTemplate();
// Retry policy
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
template.setRetryPolicy(retryPolicy);
// Backoff policy
FixedBackOffPolicy backoffPolicy = new FixedBackOffPolicy();
backoffPolicy.setBackOffPeriod(2000); // 2 seconds
template.setBackOffPolicy(backoffPolicy);
return template;
}
}The auto-configuration will detect this bean and skip creating its own due to @ConditionalOnMissingBean.
Provide your own ResponseErrorHandler bean:
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.HttpStatusCode;
import org.springframework.ai.retry.TransientAiException;
import org.springframework.ai.retry.NonTransientAiException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class CustomErrorHandlerConfig {
/**
* Custom ResponseErrorHandler bean
* Overrides auto-configured bean due to @ConditionalOnMissingBean
*/
@Bean
public ResponseErrorHandler responseErrorHandler() {
return new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
HttpStatusCode statusCode = response.getStatusCode();
// Custom logic: treat 404 as non-error
if (statusCode.value() == 404) {
return false;
}
return statusCode.isError();
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
int statusCode = response.getStatusCode().value();
String body = readBody(response);
String message = "HTTP " + statusCode + " - " + body;
// Custom classification logic
switch (statusCode) {
case 429: // Rate limit
case 503: // Service unavailable
throw new TransientAiException(message);
case 401: // Unauthorized
case 403: // Forbidden
throw new NonTransientAiException(message);
default:
if (statusCode >= 500) {
throw new TransientAiException(message);
} else {
throw new NonTransientAiException(message);
}
}
}
private String readBody(ClientHttpResponse response) throws IOException {
// Read response body
return "error details";
}
};
}
}Add additional configuration alongside the auto-configuration:
import org.springframework.retry.RetryListener;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
@Configuration
public class AdditionalRetryConfig {
/**
* Custom RetryListener for metrics/logging
* Note: This doesn't override the auto-configured RetryTemplate
* To use this listener, manually add it to RetryTemplate or create custom bean
*/
@Bean
public RetryListener customRetryListener(MeterRegistry meterRegistry) {
Counter retryCounter = meterRegistry.counter("ai.retry.attempts");
Counter failureCounter = meterRegistry.counter("ai.retry.failures");
return new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(
RetryContext context,
RetryCallback<T, E> callback) {
// Called before first attempt
return true; // true = proceed with retry
}
@Override
public <T, E extends Throwable> void close(
RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
// Called after all attempts complete
if (throwable != null) {
failureCounter.increment();
}
}
@Override
public <T, E extends Throwable> void onError(
RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
// Called after each failed attempt
retryCounter.increment();
System.out.println("Retry attempt " + (context.getRetryCount() + 1) +
" failed: " + throwable.getMessage());
}
};
}
}The auto-configuration only activates when RetryUtils.class is on the classpath:
@ConditionalOnClass(RetryUtils.class)This happens automatically when the spring-ai-retry dependency is included:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-retry</artifactId>
</dependency>If spring-ai-retry is not on the classpath, the entire auto-configuration is skipped.
Both bean factory methods use @ConditionalOnMissingBean:
@Bean
@ConditionalOnMissingBean
public RetryTemplate retryTemplate(SpringAiRetryProperties properties);
@Bean
@ConditionalOnMissingBean
public ResponseErrorHandler responseErrorHandler(SpringAiRetryProperties properties);Behavior:
RetryTemplate bean exists: auto-configuration creates oneRetryTemplate bean already exists: auto-configuration skips creationResponseErrorHandlerThis allows custom beans to override auto-configured beans.
The auto-configuration attempts to load WebClientRequestException at runtime:
// Inside retryTemplate() method
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
// No error or warning - this is expected behavior
}This allows the same auto-configuration to work with or without WebFlux:
With WebFlux dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>Result: RetryTemplate retries on TransientAiException, ResourceAccessException, WebClientRequestException
Without WebFlux: Result: RetryTemplate retries on TransientAiException, ResourceAccessException
This auto-configuration module is designed to integrate seamlessly with Spring AI components:
AI Model Clients: Spring AI's model client beans can inject the auto-configured RetryTemplate
public OpenAiClient(RetryTemplate retryTemplate)RestTemplate/WebClient: AI clients that use these HTTP clients benefit from the ResponseErrorHandler
setErrorHandler()Error Classification: TransientAiException and NonTransientAiException provide semantic error types for AI operations
Configuration: All retry behavior is configurable through standard Spring Boot properties
spring.ai.retryThe auto-configuration ensures that retry behavior is consistent across all AI operations in a Spring AI application.
The auto-configuration is registered via Spring Boot's auto-configuration mechanism:
File location: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
File content:
org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfigurationSpring Boot automatically discovers and applies this auto-configuration when:
Discovery process:
The auto-configuration is tightly integrated with SpringAiRetryProperties via the @EnableConfigurationProperties annotation:
@EnableConfigurationProperties({ SpringAiRetryProperties.class })This ensures that:
Properties are bound from application.properties or application.yml
spring.ai.retryProperties are validated at startup
Properties are injected into the bean factory methods
retryTemplate(SpringAiRetryProperties properties)Changes to properties (in dev mode) trigger bean recreation
See Configuration Properties for detailed property documentation.
tessl i tessl/maven-org-springframework-ai--spring-ai-autoconfigure-retry@1.1.1