openzipkin/zipkin · error · IllegalArgumentException

ttl <= 0

Error message

ttl <= 0

What it means

DelayLimiter.Builder.build() throws IllegalArgumentException('ttl <= 0') when the configured TTL is zero or negative (defaults to 0, so a builder that never calls ttl() fails here too). A suppression window of non-positive length is meaningless — shouldInvoke would never suppress, so the builder forces you to configure a real duration. A companion check rejects cardinality <= 0.

Source

Thrown at zipkin/src/main/java/zipkin2/internal/DelayLimiter.java:44

     * expires.
     */
    public Builder ttl(long ttl, TimeUnit ttlUnit) {
      if (ttlUnit == null) throw new NullPointerException("ttlUnit == null");
      this.ttl = ttl;
      this.ttlUnit = ttlUnit;
      return this;
    }

    /**
     * This bounds suppressions, useful because contexts can be accidentally unlimited cardinality.
     */
    public Builder cardinality(int cardinality) {
      this.cardinality = cardinality;
      return this;
    }

    public <C> DelayLimiter<C> build() {
      if (ttl <= 0L) throw new IllegalArgumentException("ttl <= 0");
      if (cardinality <= 0) throw new IllegalArgumentException("cardinality <= 0");
      return new DelayLimiter<>(new SuppressionFactory(ttlUnit.toNanos(ttl)), cardinality);
    }

    Builder() {
    }
  }

  final SuppressionFactory suppressionFactory;
  final ConcurrentHashMap<C, Suppression<C>> cache = new ConcurrentHashMap<>();
  final DelayQueue<Suppression<C>> suppressions = new DelayQueue<>();
  final int cardinality;

  DelayLimiter(SuppressionFactory suppressionFactory, int cardinality) {
    this.suppressionFactory = suppressionFactory;
    this.cardinality = cardinality;
  }

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Set a positive ttl before build(), e.g. .ttl(1, TimeUnit.MILLISECONDS) minimum.
  2. Check the property wiring: missing/renamed keys silently leave the 0 default — validate at startup that the parsed ttl > 0.
  3. If the limiter is optional in your context, only build it when a positive ttl is configured.

Example fix

// before
DelayLimiter<C> limiter = DelayLimiter.newBuilder().cardinality(10_000).build();

// after
DelayLimiter<C> limiter = DelayLimiter.newBuilder()
    .ttl(cfg.ttlMillis > 0 ? cfg.ttlMillis : 1, TimeUnit.MILLISECONDS)
    .cardinality(10_000)
    .build();
Defensive patterns

Strategy: validation

Validate before calling

if (cfg.ttlMillis <= 0) throw new ConfigException("delayLimiter.ttlMillis must be > 0");
DelayLimiter<C> limiter = DelayLimiter.newBuilder()
    .ttl(cfg.ttlMillis, TimeUnit.MILLISECONDS).cardinality(n).build();

Prevention

When it happens

Trigger: Calling build() without ever calling ttl(...), or with ttl(0, ...) / a negative config value; commonly after a config property was renamed or not loaded, leaving ttl at its 0 default.

Common situations: Internal storage components (e.g. service/endpoint lookup limiting) configured from properties where the ttl key is missing, misnamed, or parsed as 0 after a version upgrade; test builders that only set cardinality.

Related errors


AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14). Data as JSON: /api/errors/76963a2e5fe186c6. Report an issue: GitHub.