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

  1. Pass a ttl >= 1 (milliseconds) when calling put with TTL.
  2. Validate/clamp the TTL before calling: if (ttl <= 0) throw or use a sane default.
  3. 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

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


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/a047d0338ac7e2d9. Report an issue: GitHub.