openzipkin/zipkin · error · IllegalArgumentException
Start bucket ({startBucket}) > end bucket ({endBucket})
Error message
Start bucket ({startBucket}) > end bucket ({endBucket}) What it means
CassandraSpanStore.newBucketedTraceIdCall throws IllegalArgumentException ('Start bucket ({startBucket}) > end bucket ({endBucket})') when the duration-index bucket computed from the query range's start millis is greater than the bucket computed from its end millis. Buckets are monotonic in time, so this means the derived timestamp range is inverted (end before start), normally caused by an endTs/lookback combination that produces a negative or zero-width window.
Source
Thrown at zipkin-storage/cassandra/src/main/java/zipkin2/storage/cassandra/CassandraSpanStore.java:183
* <p>The result will be an aggregate if the input request serviceName is null, both span name
* and remote service name are supplied, or there's more than one day of data in the timestamp
* range.
*
* <p>Note that when {@link QueryRequest#serviceName()} is null, the returned query composes over
* {@link #getServiceNames()}. This means that if you have 1000 service names, you will end up
* with a composition of at least 1000 calls.
*/
// TODO: smartly handle when serviceName is null. For example, rank recently written serviceNames
// and speculatively query those first.
Call<Map<String, Long>> newBucketedTraceIdCall(
QueryRequest request, TimestampRange timestampRange, int traceIndexFetchSize) {
// trace_by_service_span adds special empty-string span name in order to search by all
String spanName = null != request.spanName() ? request.spanName() : "";
Long minDuration = request.minDuration(), maxDuration = request.maxDuration();
int startBucket = durationIndexBucket(timestampRange.startMillis * 1000);
int endBucket = durationIndexBucket(timestampRange.endMillis * 1000);
if (startBucket > endBucket) {
throw new IllegalArgumentException(
"Start bucket (" + startBucket + ") > end bucket (" + endBucket + ")");
}
// "" isn't a real value. it is used to template bucketed calls and replaced later
String serviceName = null != request.serviceName() ? request.serviceName() : "";
// TODO: ideally, the buckets are traversed backwards, only spawning queries for older buckets
// if younger buckets are empty. This will be an async continuation, punted for now.
List<SelectTraceIdsFromServiceSpan.Input> serviceSpans = new ArrayList<>();
List<SelectTraceIdsFromServiceRemoteService.Input> serviceRemoteServices = new ArrayList<>();
String remoteService = request.remoteServiceName();
for (int bucket = endBucket; bucket >= startBucket; bucket--) {
boolean addSpanQuery = true;
if (remoteService != null) {
if (traceIdsFromServiceRemoteService == null) {
throw new IllegalArgumentException("remoteService=" + remoteService
+ " unsupported due to missing table " + TABLE_SERVICE_REMOTE_SERVICES);
}View on GitHub (pinned to 878ce2a1fa)
Solutions
- Send endTs as epoch milliseconds and lookback as a positive millisecond window with lookback <= endTs
- Validate endTs > 0 and endTs - lookback >= 0 before calling getTraces
- If defaulting values, clamp lookback to endTs when no explicit endTs is set
Example fix
// before long endTs = 1699999999; // seconds, not millis -> tiny window long lookback = 86400000L; queryRequest = QueryRequest.newBuilder().endTs(endTs).lookback(lookback).build(); // after long endTs = 1699999999000L; // epoch millis long lookback = 86400000L; // one day in millis queryRequest = QueryRequest.newBuilder().endTs(endTs).lookback(lookback).build();
Defensive patterns
Strategy: validation
Validate before calling
long endTs = request.endTs() > 0 ? request.endTs() : System.currentTimeMillis(); long lookback = request.lookback() > 0 ? request.lookback() : 86400000L; if (lookback > endTs) lookback = endTs; // clamp to epoch so startMillis >= 0
Prevention
- Always use epoch milliseconds for endTs/lookback
- Clamp lookback to endTs at the API boundary so the window never crosses the epoch
When it happens
Trigger: A QueryRequest whose endTs and lookback produce endMillis < startMillis (e.g. lookback larger than endTs, or endTs=0 with defaults), fed into getTraces on Cassandra storage; durationIndexBucket then maps start to a higher bucket than end.
Common situations: Passing millisecond values where microseconds or epoch-seconds were expected, so endTs is tiny; computing lookback incorrectly so the window crosses the epoch; UI or scripts sending lookback > endTs.
Related errors
- endTs <= 0
- key == null
- {annotationQueryString} query unsupported due to missing ann
- remoteService={remoteService} unsupported due to missing tab
- maxConnections <= 0
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/d12a3cf5b4f03068.
Report an issue: GitHub.