alibaba/spring-ai-alibaba · error · IllegalArgumentException

ttl must be positive or -1 (no expiration), got:

Error message

ttl must be positive or -1 (no expiration), got: 

What it means

RedisSaver.Builder.ttl(long, TimeUnit) only accepts a positive ttl or -1 meaning 'no expiration'. Passing 0 or any value less than -1 throws IllegalArgumentException with this message including the offending value.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/redis/RedisSaver.java:474

		/**
		 * Sets the time-to-live (TTL) for all Redis keys managed by RedisSaver.
		 * When set, checkpoint data, thread metadata, and reverse mappings will
		 * automatically expire after the specified duration.
		 * <p>
		 * Default is -1 (no expiration), which preserves backward compatibility.
		 *
		 * @param ttl the time-to-live value, must be positive; -1 means no expiration
		 * @param ttlUnit the time unit for ttl, must not be null
		 * @return this builder
		 * @throws IllegalArgumentException if ttlUnit is null or ttl is 0 or less than -1
		 */
		public Builder ttl(long ttl, TimeUnit ttlUnit) {
			if (ttlUnit == null) {
				throw new IllegalArgumentException("ttlUnit cannot be null");
			}
			if (ttl == 0 || ttl < -1) {
				throw new IllegalArgumentException("ttl must be positive or -1 (no expiration), got: " + ttl);
			}
			this.ttl = ttl;
			this.ttlUnit = ttlUnit;
			return this;
		}

		/**
		 * Builds a new RedisSaver instance.
		 * @return a new RedisSaver instance
		 * @throws IllegalArgumentException if redisson or stateSerializer is null
		 */
		public RedisSaver build() {
			if (redisson == null) {
				throw new IllegalArgumentException("redisson cannot be null");
			}
			if (stateSerializer == null) {
				this.stateSerializer = StateGraph.DEFAULT_JACKSON_SERIALIZER;
			}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Use -1 instead of 0 when you want checkpoints to never expire: ttl(-1, TimeUnit.SECONDS).
  2. Clamp/validate the configured ttl before building: if (ttl == 0) ttl = -1; or reject non-positive config values at startup.
  3. Pass a positive ttl (e.g. 3600 seconds) if checkpoints should actually expire.

Example fix

// before
RedisSaver saver = new RedisSaver.Builder().ttl(ttl, TimeUnit.SECONDS).build(); // ttl = 0
// after
long effectiveTtl = (ttl == 0) ? -1 : ttl;
RedisSaver saver = new RedisSaver.Builder().ttl(effectiveTtl, TimeUnit.SECONDS).build();
Defensive patterns

Strategy: validation

Validate before calling

if (ttl == 0 || ttl < -1) {
    throw new IllegalStateException("checkpoint ttl must be > 0 or -1 (no expiration), got: " + ttl);
}

Prevention

When it happens

Trigger: new RedisSaver.Builder().ttl(0, TimeUnit.SECONDS) or ttl(-5, TimeUnit.SECONDS) — typically when ttl is read from configuration that defaults to 0 or is computed to a negative value.

Common situations: Config property checkpoint.ttl=0 interpreted as 'forever' by the developer but rejected by the builder (they must use -1 for no expiration); arithmetic producing negative TTLs; unit tests with unset ttl fields 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/4e03b4cc62317560. Report an issue: GitHub.