alibaba/spring-ai-alibaba · error · IllegalArgumentException
maxAttempts must be >= 1
Error message
maxAttempts must be >= 1
What it means
ToolRetryInterceptor.Builder.maxAttempts() requires at least 1, since the count includes the initial call; 0 or negative would mean the tool is never called. Values < 1 throw IllegalArgumentException immediately.
Source
Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java:181
public static class Builder {
// Total number of attempts including the first call.
private int maxAttempts = 3;
private Set<String> toolNames;
private Predicate<Exception> retryOn = e -> true; // Retry on all exceptions by default
private OnFailureBehavior onFailure = OnFailureBehavior.RETURN_MESSAGE;
private Function<Exception, String> errorFormatter;
private double backoffFactor = 2.0;
private long initialDelayMs = 1000;
private long maxDelayMs = 60000;
private boolean jitter = true;
/**
* Set the maximum number of attempts, including the first call. Must be >= 1.
* @param maxAttempts total attempts (initial call plus retries)
*/
public Builder maxAttempts(int maxAttempts) {
if (maxAttempts < 1) {
throw new IllegalArgumentException("maxAttempts must be >= 1");
}
this.maxAttempts = maxAttempts;
return this;
}
/**
* Set the maximum number of retries (excluding the first call).
* @param maxRetries number of retries; total attempts will be {@code maxRetries + 1}
* @deprecated use {@link #maxAttempts(int)} instead, which counts the initial call
*/
@Deprecated
public Builder maxRetries(int maxRetries) {
if (maxRetries < 0) {
throw new IllegalArgumentException("maxRetries must be >= 0");
}
this.maxAttempts = maxRetries + 1;
return this;
}View on GitHub (pinned to f82da0b50f)
Solutions
- Pass maxAttempts >= 1 (use 1 to disable retries entirely)
- Convert "retry count" config (0 = no retries) to attempts = retries + 1
- Guard/default the config value before building
Example fix
// before ToolRetryInterceptor.builder().maxAttempts(0) // meant: no retries // after ToolRetryInterceptor.builder().maxAttempts(1) // 1 = initial call only, no retries
Defensive patterns
Strategy: validation
Validate before calling
int attempts = Math.max(1, configuredRetries + 1); ToolRetryInterceptor.builder().maxAttempts(attempts).build();
Type guard
boolean isValidMaxAttempts(int n) { return n >= 1; } Try / catch
try { builder.maxAttempts(v); } catch (IllegalArgumentException e) { log.warn("Bad maxAttempts '{}', using 1", v); builder.maxAttempts(1); } Prevention
- Remember attempts includes the first call; retries = attempts - 1
- Convert retry-count configs with + 1
- Default unset config to 1 instead of 0
When it happens
Trigger: Calling Builder.maxAttempts(0) or a negative int, often from config where maxAttempts was set to 0 meaning "no retries" by the author.
Common situations: Config value of 0 intended as "disable retries" (correct value is 1); off-by-one confusion between attempts and retries; unset config defaulting to 0.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- maxAttempts must be greater than or equal to 1
- maxDelay must be greater than or equal to 0.
- The backoffMultiplier must be >= 1.0
- maxRetries must be >= 0
- maxRetries must be non-negative
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/991303595dfcae35.
Report an issue: GitHub.