spring-projects/spring-ai · error · NonTransientAiException
%s - %s
Error message
%s - %s
What it means
RetryUtils' handleError converts an RestClientResponseException from an AI API call into either NonTransientAiException (for 4xx client errors like bad API keys, quota exceeded) or TransientAiException (5xx/other), using a message of the form '<statusCode> - <statusText>/<body>'.
Source
Thrown at spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java:90
@Override
public void handleError(final URI url, final HttpMethod method, final ClientHttpResponse response)
throws IOException {
handleError(response);
}
@SuppressWarnings("removal")
public void handleError(final ClientHttpResponse response) throws IOException {
if (response.getStatusCode().isError()) {
String error = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
String message = String.format("%s - %s", response.getStatusCode().value(), error);
/*
* Thrown on 4xx client errors, such as 401 - Incorrect API key provided,
* 401 - You must be a member of an organization to use the API, 429 -
* Rate limit reached for requests, 429 - You exceeded your current quota,
* please check your plan and billing details.
*/
if (response.getStatusCode().is4xxClientError()) {
throw new NonTransientAiException(message);
}
throw new TransientAiException(message);
}
}
};
/**
* Default RetryTemplate with exponential backoff configuration.
*/
public static final RetryTemplate DEFAULT_RETRY_TEMPLATE = createDefaultRetryTemplate();
/**
* Short RetryTemplate for testing scenarios.
*/
public static final RetryTemplate SHORT_RETRY_TEMPLATE = createShortRetryTemplate();
private static RetryTemplate createDefaultRetryTemplate() {View on GitHub (pinned to 98a7beda4f)
Solutions
- For 401: correct the API key/credentials in configuration (OPENAI_API_KEY or equivalent property)
- For 429: add rate limiting/backoff, reduce request concurrency, or upgrade your plan/quota
- For 5xx TransientAiException: rely on RetryUtils' RetryTemplate backoff or increase retries
- Log the full message body to see the provider's specific error and fix the request payload accordingly
Example fix
// before
String key = System.getenv().get("OPEN_API_KEY"); // wrong env var name -> 401
// after
String key = System.getenv().get("OPENAI_API_KEY"); Defensive patterns
Strategy: try-catch
Validate before calling
// verify credentials before calling
if (apiKey == null || apiKey.isBlank()) throw new IllegalStateException("API key not configured"); Try / catch
try { result = retryUtils.executeWithRetry(callback); }
catch (NonTransientAiException e) {
// 4xx: do NOT retry — fix key/quota/request
} catch (TransientAiException e) {
// 5xx: safe to retry with backoff
} Prevention
- Validate API keys at startup with a cheap health/list-models call
- Implement client-side rate limiting to avoid 429s
- Log full response bodies for provider-specific error details
- Monitor quota usage proactively
When it happens
Trigger: An HTTP call to a model provider (OpenAI, Azure, etc.) inside a retryable operation fails: a 4xx response (401 invalid API key, 429 rate limit/quota) yields NonTransientAiException; a 5xx or other error yields TransientAiException with message '%s - %s'.
Common situations: Expired or wrong API keys (401); hitting rate limits or exceeding quota (429); provider-side outages (500/503) during high load; malformed requests rejected by the provider (400).
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- HTTP %s - %s
- Stateless Streamable-Http prompt method must not declare par
- Failed to initialize ChromaVectorStore
- Failed to delete documents by filter
- Got an unexpected HTTP error: %s
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/3fed10c044c3298b.
Report an issue: GitHub.