alibaba/spring-ai-alibaba · error

Exceptions cannot be retried and are thrown immediately: {}

Error message

Exceptions cannot be retried and are thrown immediately: {}

What it means

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).

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelretry/ModelRetryInterceptor.java:139

				// Successful response
				if (attempt > 1) {
					log.info("The model call succeeded after the {}th attempt.", attempt);
				}
				return modelResponse;

			} catch (Exception e) {
				lastException = e;
				log.warn("Model call failed (attempted {}/{}): {}", attempt, maxAttempts, e.getMessage());

				if (attempt >= maxAttempts) {
					log.error("The maximum number of retries has been reached {}, and the model call has failed.", maxAttempts);
					throw new RuntimeException("Model call failed, maximum number of retries reached.", lastException);
				}

				// Determine if an exception can be retried.
				if (!retryableExceptionPredicate.test(e)) {
					log.warn("Exceptions cannot be retried and are thrown immediately: {}", e.getMessage());
					throw new RuntimeException("Model call failed (non-retryable exception)", e);
				}

				// Wait and try again
				if (currentDelay > 0) {
					try {
						log.info("Retry after {} ms", currentDelay);
						Thread.sleep(currentDelay);
					} catch (InterruptedException ie) {
						Thread.currentThread().interrupt();
						throw new RuntimeException("Retry interrupted", ie);
					}
				}

				// Calculate the next delay time (exponential backoff)
				currentDelay = Math.min((long) (currentDelay * backoffMultiplier), maxDelay);
			}
		}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Inspect the cause chain (getCause()) to find the real upstream exception and fix it — the wrapping RuntimeException is only the interceptor's envelope.
  2. Widen retryableExceptionPredicate to include the exception type you want retried (e.g. e -> e instanceof IOException || e instanceof TransientDataAccessException).
  3. Fix the root configuration: correct the API key, model name, or request payload so the model call stops failing.
  4. If the failure is genuinely transient but misclassified, add the specific status-code/exception check to the predicate instead of retrying everything blindly.

Example fix

// before
.retryableExceptionPredicate(e -> false)
// after
.retryableExceptionPredicate(e ->
    e instanceof java.io.IOException
    || (e instanceof org.springframework.web.client.HttpServerErrorException hse && hse.getStatusCode().is5xxServerError()))
Defensive patterns

Strategy: try-catch

Validate before calling

if (!(options.getRetryableExceptionPredicate() instanceof Predicate)) {
    throw new IllegalStateException("Configure retryableExceptionPredicate before running the agent");
}

Type guard

static boolean isRetryable(Exception e) {
    return e instanceof java.io.IOException
        || (e instanceof org.springframework.web.client.HttpStatusCodeException hce && hce.getStatusCode().is5xxServerError());
}

Try / catch

try {
    return interceptModel(request, handler);
} catch (RuntimeException e) {
    Throwable root = e.getCause() != null ? e.getCause() : e;
    log.error("Model call failed (possibly non-retryable): {}", root.getMessage(), root);
    throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/7c73b3c41986285a. Report an issue: GitHub.