{"record":{"id":"3fed10c044c3298b","repo":"spring-projects/spring-ai","slug":"s-s","errorCode":null,"errorMessage":"%s - %s","messagePattern":"%s - %s","errorType":"http","errorClass":"NonTransientAiException","httpStatus":null,"severity":"error","filePath":"spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java","lineNumber":90,"sourceCode":"\t\t@Override\n\t\tpublic void handleError(final URI url, final HttpMethod method, final ClientHttpResponse response)\n\t\t\t\tthrows IOException {\n\t\t\thandleError(response);\n\t\t}\n\n\t\t@SuppressWarnings(\"removal\")\n\t\tpublic void handleError(final ClientHttpResponse response) throws IOException {\n\t\t\tif (response.getStatusCode().isError()) {\n\t\t\t\tString error = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);\n\t\t\t\tString message = String.format(\"%s - %s\", response.getStatusCode().value(), error);\n\t\t\t\t/*\n\t\t\t\t * Thrown on 4xx client errors, such as 401 - Incorrect API key provided,\n\t\t\t\t * 401 - You must be a member of an organization to use the API, 429 -\n\t\t\t\t * Rate limit reached for requests, 429 - You exceeded your current quota,\n\t\t\t\t * please check your plan and billing details.\n\t\t\t\t */\n\t\t\t\tif (response.getStatusCode().is4xxClientError()) {\n\t\t\t\t\tthrow new NonTransientAiException(message);\n\t\t\t\t}\n\t\t\t\tthrow new TransientAiException(message);\n\t\t\t}\n\t\t}\n\n\t};\n\n\t/**\n\t * Default RetryTemplate with exponential backoff configuration.\n\t */\n\tpublic static final RetryTemplate DEFAULT_RETRY_TEMPLATE = createDefaultRetryTemplate();\n\n\t/**\n\t * Short RetryTemplate for testing scenarios.\n\t */\n\tpublic static final RetryTemplate SHORT_RETRY_TEMPLATE = createShortRetryTemplate();\n\n\tprivate static RetryTemplate createDefaultRetryTemplate() {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java#L72-L108","documentation":"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>'.","triggerScenarios":"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'.","commonSituations":"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).","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"],"exampleFix":"// before\nString key = System.getenv().get(\"OPEN_API_KEY\"); // wrong env var name -> 401\n// after\nString key = System.getenv().get(\"OPENAI_API_KEY\");","handlingStrategy":"try-catch","validationCode":"// verify credentials before calling\nif (apiKey == null || apiKey.isBlank()) throw new IllegalStateException(\"API key not configured\");","typeGuard":null,"tryCatchPattern":"try { result = retryUtils.executeWithRetry(callback); }\ncatch (NonTransientAiException e) {\n    // 4xx: do NOT retry — fix key/quota/request\n} catch (TransientAiException e) {\n    // 5xx: safe to retry with backoff\n}","preventionTips":["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"],"tags":["retry","http","api-error"],"backgroundTag":"http-error-response","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}