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
- Default the unit when absent: unit != null ? unit : TimeUnit.MILLISECONDS.
- Validate the parsed unit string at config load time with a whitelist (ms/s/m) and fail fast there.
- 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
- Map config strings to TimeUnit via a whitelist (ms/s/m) with a default.
- Fail fast on unknown unit names at config load, not inside builders.
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.