eclipse-vertx/vert.x · error · IllegalArgumentException
ttl must be positive: ${ttl}
Error message
ttl must be positive: ${ttl} What it means
LocalAsyncMapImpl.Holder wraps values that carry a time-to-live. The constructor requires ttl >= 1 because a zero/negative TTL cannot produce a valid expiration timer; it throws IllegalArgumentException otherwise.
Source
Thrown at vertx-core/src/main/java/io/vertx/core/shareddata/impl/LocalAsyncMapImpl.java:259
}
}
private static class Holder<V> {
final V value;
final long timerId;
final long ttl;
final long timestamp;
Holder(V value) {
Objects.requireNonNull(value);
this.value = value;
timestamp = ttl = timerId = 0;
}
Holder(V value, long timerId, long ttl, long timestamp) {
Objects.requireNonNull(value);
if (ttl < 1) {
throw new IllegalArgumentException("ttl must be positive: " + ttl);
}
this.value = value;
this.timerId = timerId;
this.ttl = ttl;
this.timestamp = timestamp;
}
boolean expires() {
return ttl > 0;
}
boolean hasNotExpired() {
return !expires() || MILLISECONDS.convert(System.nanoTime() - timestamp, NANOSECONDS) < ttl;
}
@Override
public String toString() {
return "Holder{" + "value=" + value + ", timerId=" + timerId + ", ttl=" + ttl + ", timestamp=" + timestamp + '}';View on GitHub (pinned to fb308bd8c3)
Solutions
- Pass a ttl >= 1 (milliseconds) when calling put with TTL.
- Validate/clamp the TTL before calling: if (ttl <= 0) throw or use a sane default.
- Fix the configuration source so the TTL property is set to a positive number.
Example fix
// before
map.put("key", value, ttlFromConfig); // ttlFromConfig == 0 -> IllegalArgumentException
// after
if (ttlFromConfig <= 0) ttlFromConfig = 60_000L;
map.put("key", value, ttlFromConfig); Defensive patterns
Strategy: validation
Validate before calling
if (ttl <= 0) throw new IllegalArgumentException("ttl must be positive, got " + ttl);
map.put(key, value, ttl); Try / catch
try {
map.put(key, value, ttl);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("ttl must be positive")) {
map.put(key, value, defaultTtl);
} else throw e;
} Prevention
- Validate TTL values loaded from config before use (must be >= 1).
- Use a helper method that clamps or defaults non-positive TTLs.
- Keep TTL units documented (milliseconds in Vert.x) to avoid zero conversions.
When it happens
Trigger: Calling LocalAsyncMap.put(k, v, ttl) with ttl <= 0, or constructing a Holder directly with a non-positive ttl.
Common situations: A computed TTL comes from configuration that defaulted to 0 (e.g. missing 'cache.ttl' property) or from a subtraction/calculation that produced 0 or a negative value.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Cannot put null in key or value of async map
- Invalid type: ${obj.getClass().getName()} to put in async ma
- Invalid type for shareddata data structure: ${obj.getClass()
- Can't get cluster wide map if not clustered
- Can't register a system codec
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/a047d0338ac7e2d9.
Report an issue: GitHub.