openzipkin/zipkin · error · NullPointerException

ttlUnit == null

Error message

ttlUnit == null

What it means

DelayLimiter.Builder.ttl(long, TimeUnit) throws NullPointerException('ttlUnit == null') when the time unit argument is null. The ttl value itself may be anything at this point; only the unit is null-checked at setter time because the duration cannot be interpreted without it. DelayLimiter is an internal rate-suppression helper used by zipkin storage components.

Source

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

/** Limits invocations of a given context to at most once per period. */
// this is a dependency-free variant formerly served by an expiring guava cache
public final class DelayLimiter<C> {
  public static Builder newBuilder() {
    return new Builder();
  }

  public static final class Builder {
    long ttl = 0L;
    TimeUnit ttlUnit = TimeUnit.MILLISECONDS;
    int cardinality = 0;

    /**
     * When {@link #shouldInvoke(Object)} returns true, it will return false until this duration
     * 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);
    }

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Default the unit when absent: unit != null ? unit : TimeUnit.MILLISECONDS.
  2. Validate the parsed unit string at config load time with a whitelist (ms/s/m) and fail fast there.
  3. Use TimeUnit.valueOf(name.toUpperCase(Locale.ROOT)) guarded by try-catch or a Set of valid names.

Example fix

// before
builder.ttl(ttl, unitByName.get(cfg.unit)); // null on unknown name

// after
TimeUnit unit = unitByName.getOrDefault(cfg.unit, TimeUnit.MILLISECONDS);
builder.ttl(ttl, unit);
Defensive patterns

Strategy: validation

Validate before calling

TimeUnit unit = configured != null ? configured : TimeUnit.MILLISECONDS;
builder.ttl(ttl, unit);

Prevention

When it happens

Trigger: Calling .ttl(ttl, unit) where unit came from a config lookup, map.get, or enum valueOf that returned null (e.g. TimeUnit.valueOf("MILLIS") with wrong case throws earlier, but a Map<String,TimeUnit> miss returns null silently).

Common situations: Config-driven code that parses a unit string from properties/YAML into a TimeUnit and passes the result unchecked; Optional-style chains where the unit is only sometimes set.

Related errors


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