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
- Use -1 instead of 0 when you want checkpoints to never expire: ttl(-1, TimeUnit.SECONDS).
- Clamp/validate the configured ttl before building: if (ttl == 0) ttl = -1; or reject non-positive config values at startup.
- 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
- Remember -1 (not 0) means 'no expiration' in this builder.
- Validate ttl in configuration binding (e.g. @Min/@Custom validator) before building the saver.
- Normalize 0 from external config to -1 or a positive default at load time.
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
- ttlUnit cannot be null
- redisson cannot be null
- Name must be provided
- Description must be provided
- AgentCard or AgentCardProvider must be provided
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/4e03b4cc62317560.
Report an issue: GitHub.