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

  1. For 401: correct the API key/credentials in configuration (OPENAI_API_KEY or equivalent property)
  2. For 429: add rate limiting/backoff, reduce request concurrency, or upgrade your plan/quota
  3. For 5xx TransientAiException: rely on RetryUtils' RetryTemplate backoff or increase retries
  4. 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

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


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/3fed10c044c3298b. Report an issue: GitHub.