openzipkin/zipkin · error · IllegalArgumentException
endTs <= 0
Error message
endTs <= 0
What it means
QueryRequest.Builder.build() validates the assembled query: endTs (epoch millis), limit, and lookback must all be positive, minDuration/maxDuration must be positive microseconds and consistent, else IllegalArgumentException ('endTs <= 0' here). QueryRequest is the value type behind zipkin-server's GET /spans-ish search API, so this fires while constructing a search, not while executing it.
Source
Thrown at zipkin/src/main/java/zipkin2/storage/QueryRequest.java:250
}
/** Sets {@link QueryRequest#limit()} */
public Builder limit(int limit) {
this.limit = limit;
return this;
}
public QueryRequest build() {
// coerce service and span names to lowercase
if (serviceName != null) serviceName = serviceName.toLowerCase(Locale.ROOT);
if (remoteServiceName != null) remoteServiceName = remoteServiceName.toLowerCase(Locale.ROOT);
if (spanName != null) spanName = spanName.toLowerCase(Locale.ROOT);
if ("".equals(serviceName)) serviceName = null;
if ("".equals(remoteServiceName)) remoteServiceName = null;
if ("".equals(spanName) || "all".equals(spanName)) spanName = null;
if (endTs <= 0) throw new IllegalArgumentException("endTs <= 0");
if (limit <= 0) throw new IllegalArgumentException("limit <= 0");
if (lookback <= 0) throw new IllegalArgumentException("lookback <= 0");
if (minDuration != null) {
if (minDuration <= 0) throw new IllegalArgumentException("minDuration <= 0");
if (maxDuration != null && maxDuration < minDuration) {
throw new IllegalArgumentException("maxDuration < minDuration");
}
} else if (maxDuration != null) {
throw new IllegalArgumentException("maxDuration is only valid with minDuration");
}
return new QueryRequest(
serviceName,
remoteServiceName,
spanName,
annotationQuery,
minDuration,
maxDuration,View on GitHub (pinned to 878ce2a1fa)
Solutions
- Always set endTs (and limit, lookback): QueryRequest.newBuilder().endTs(System.currentTimeMillis()).lookback(86400000L).limit(10).serviceName(...).build().
- Validate/parse HTTP params at the edge and reject blank values with 400 instead of forwarding 0.
- Double-check units: everything is milliseconds except durations, which are microseconds.
- Centralize QueryRequest construction in one factory so no call site can forget endTs.
Example fix
// before
QueryRequest q = QueryRequest.newBuilder().serviceName("web").limit(10).build(); // endTs=0 -> throws
// after
QueryRequest q = QueryRequest.newBuilder().serviceName("web")
.endTs(System.currentTimeMillis())
.lookback(86_400_000L)
.limit(10)
.build(); Defensive patterns
Strategy: validation
Validate before calling
long now = System.currentTimeMillis();
QueryRequest q = QueryRequest.newBuilder().serviceName("web").endTs(now).lookback(86_400_000L).limit(10).build(); Type guard
boolean canBuildQuery(long endTs, long limit, long lookback) { return endTs > 0 && limit > 0 && lookback > 0; } Prevention
- Centralize QueryRequest construction in one factory method that always sets endTs.
- Validate HTTP query params at the controller and return 400 for blank/non-numeric values.
- Remember units: timestamps/lookback in ms, durations in microseconds.
When it happens
Trigger: Building a QueryRequest where endTs was never set (primitive long default 0) or was set in seconds, or constructing the builder from HTTP parameters that were blank/non-numeric and coerced to 0.
Common situations: Clients omitting endTs expecting 'now'; proxies stripping query parameters; tests building QueryRequest.newBuilder() without endTs; second-vs-millisecond unit mistakes producing 0 after integer division.
Related errors
- Start bucket ({startBucket}) > end bucket ({endBucket})
- endTs <= 0
- limit <= 0
- input is not a list
- List<Span> implies at least traceId and id fields
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/c9b349b3535892bd.
Report an issue: GitHub.