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

  1. Pass maxAttempts >= 1 (use 1 to disable retries entirely)
  2. Convert "retry count" config (0 = no retries) to attempts = retries + 1
  3. 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

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


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