{"record":{"id":"7c73b3c41986285a","repo":"alibaba/spring-ai-alibaba","slug":"exceptions-cannot-be-retried-and-are-thrown-immedi","errorCode":null,"errorMessage":"Exceptions cannot be retried and are thrown immediately: {}","messagePattern":"Exceptions cannot be retried and are thrown immediately: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelretry/ModelRetryInterceptor.java","lineNumber":139,"sourceCode":"\n\t\t\t\t// Successful response\n\t\t\t\tif (attempt > 1) {\n\t\t\t\t\tlog.info(\"The model call succeeded after the {}th attempt.\", attempt);\n\t\t\t\t}\n\t\t\t\treturn modelResponse;\n\n\t\t\t} catch (Exception e) {\n\t\t\t\tlastException = e;\n\t\t\t\tlog.warn(\"Model call failed (attempted {}/{}): {}\", attempt, maxAttempts, e.getMessage());\n\n\t\t\t\tif (attempt >= maxAttempts) {\n\t\t\t\t\tlog.error(\"The maximum number of retries has been reached {}, and the model call has failed.\", maxAttempts);\n\t\t\t\t\tthrow new RuntimeException(\"Model call failed, maximum number of retries reached.\", lastException);\n\t\t\t\t}\n\n\t\t\t\t// Determine if an exception can be retried.\n\t\t\t\tif (!retryableExceptionPredicate.test(e)) {\n\t\t\t\t\tlog.warn(\"Exceptions cannot be retried and are thrown immediately: {}\", e.getMessage());\n\t\t\t\t\tthrow new RuntimeException(\"Model call failed (non-retryable exception)\", e);\n\t\t\t\t}\n\n\t\t\t\t// Wait and try again\n\t\t\t\tif (currentDelay > 0) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlog.info(\"Retry after {} ms\", currentDelay);\n\t\t\t\t\t\tThread.sleep(currentDelay);\n\t\t\t\t\t} catch (InterruptedException ie) {\n\t\t\t\t\t\tThread.currentThread().interrupt();\n\t\t\t\t\t\tthrow new RuntimeException(\"Retry interrupted\", ie);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Calculate the next delay time (exponential backoff)\n\t\t\t\tcurrentDelay = Math.min((long) (currentDelay * backoffMultiplier), maxDelay);\n\t\t\t}\n\t\t}","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelretry/ModelRetryInterceptor.java#L121-L157","documentation":"ModelRetryInterceptor wraps model calls with retry logic, but only exceptions accepted by the configured retryableExceptionPredicate are retried. When the predicate rejects an exception, the interceptor logs this warning and immediately rethrows it wrapped in a RuntimeException so the caller sees the failure without wasting retry attempts. It is a deliberate fast-fail for errors that retrying cannot fix (e.g. auth errors, bad requests).","triggerScenarios":"Thrown from interceptModel when the model call raises an exception and retryableExceptionPredicate.test(e) returns false; also reached after maxAttempts retries are exhausted (adjacent branch at the same call site).","commonSituations":"1) Calling a model with an invalid or expired API key (401/403) which the default predicate marks non-retryable. 2) Sending a malformed prompt or exceeding context limits (400). 3) A custom retryableExceptionPredicate that is too narrow, e.g. only matching IOException, so transient 5xx errors wrapped in other types fail fast. 4) Rate-limit errors whose exception type is not on the retryable list.","solutions":["Inspect the cause chain (getCause()) to find the real upstream exception and fix it — the wrapping RuntimeException is only the interceptor's envelope.","Widen retryableExceptionPredicate to include the exception type you want retried (e.g. e -> e instanceof IOException || e instanceof TransientDataAccessException).","Fix the root configuration: correct the API key, model name, or request payload so the model call stops failing.","If the failure is genuinely transient but misclassified, add the specific status-code/exception check to the predicate instead of retrying everything blindly."],"exampleFix":"// before\n.retryableExceptionPredicate(e -> false)\n// after\n.retryableExceptionPredicate(e ->\n    e instanceof java.io.IOException\n    || (e instanceof org.springframework.web.client.HttpServerErrorException hse && hse.getStatusCode().is5xxServerError()))","handlingStrategy":"try-catch","validationCode":"if (!(options.getRetryableExceptionPredicate() instanceof Predicate)) {\n    throw new IllegalStateException(\"Configure retryableExceptionPredicate before running the agent\");\n}","typeGuard":"static boolean isRetryable(Exception e) {\n    return e instanceof java.io.IOException\n        || (e instanceof org.springframework.web.client.HttpStatusCodeException hce && hce.getStatusCode().is5xxServerError());\n}","tryCatchPattern":"try {\n    return interceptModel(request, handler);\n} catch (RuntimeException e) {\n    Throwable root = e.getCause() != null ? e.getCause() : e;\n    log.error(\"Model call failed (possibly non-retryable): {}\", root.getMessage(), root);\n    throw e;\n}","preventionTips":["Always configure an explicit retryableExceptionPredicate covering transient error classes","Inspect cause chains, not just the wrapper message","Fix credentials/payloads before adding retries","Unit-test the predicate against the real provider exception types"],"tags":["retry","model-call","non-retryable","interceptor"],"backgroundTag":"api-request-failed","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}