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

  1. Always set endTs (and limit, lookback): QueryRequest.newBuilder().endTs(System.currentTimeMillis()).lookback(86400000L).limit(10).serviceName(...).build().
  2. Validate/parse HTTP params at the edge and reject blank values with 400 instead of forwarding 0.
  3. Double-check units: everything is milliseconds except durations, which are microseconds.
  4. 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

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


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