{"record":{"id":"d12a3cf5b4f03068","repo":"openzipkin/zipkin","slug":"start-bucket-startbucket-end-bucket-endbuc","errorCode":null,"errorMessage":"Start bucket ({startBucket}) > end bucket ({endBucket})","messagePattern":"Start bucket \\((.+?)\\) > end bucket \\((.+?)\\)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"zipkin-storage/cassandra/src/main/java/zipkin2/storage/cassandra/CassandraSpanStore.java","lineNumber":183,"sourceCode":"   * <p>The result will be an aggregate if the input request serviceName is null, both span name\n   * and remote service name are supplied, or there's more than one day of data in the timestamp\n   * range.\n   *\n   * <p>Note that when {@link QueryRequest#serviceName()} is null, the returned query composes over\n   * {@link #getServiceNames()}. This means that if you have 1000 service names, you will end up\n   * with a composition of at least 1000 calls.\n   */\n  // TODO: smartly handle when serviceName is null. For example, rank recently written serviceNames\n  // and speculatively query those first.\n  Call<Map<String, Long>> newBucketedTraceIdCall(\n    QueryRequest request, TimestampRange timestampRange, int traceIndexFetchSize) {\n    // trace_by_service_span adds special empty-string span name in order to search by all\n    String spanName = null != request.spanName() ? request.spanName() : \"\";\n    Long minDuration = request.minDuration(), maxDuration = request.maxDuration();\n    int startBucket = durationIndexBucket(timestampRange.startMillis * 1000);\n    int endBucket = durationIndexBucket(timestampRange.endMillis * 1000);\n    if (startBucket > endBucket) {\n      throw new IllegalArgumentException(\n        \"Start bucket (\" + startBucket + \") > end bucket (\" + endBucket + \")\");\n    }\n\n    // \"\" isn't a real value. it is used to template bucketed calls and replaced later\n    String serviceName = null != request.serviceName() ? request.serviceName() : \"\";\n\n    // TODO: ideally, the buckets are traversed backwards, only spawning queries for older buckets\n    // if younger buckets are empty. This will be an async continuation, punted for now.\n    List<SelectTraceIdsFromServiceSpan.Input> serviceSpans = new ArrayList<>();\n    List<SelectTraceIdsFromServiceRemoteService.Input> serviceRemoteServices = new ArrayList<>();\n    String remoteService = request.remoteServiceName();\n    for (int bucket = endBucket; bucket >= startBucket; bucket--) {\n      boolean addSpanQuery = true;\n      if (remoteService != null) {\n        if (traceIdsFromServiceRemoteService == null) {\n          throw new IllegalArgumentException(\"remoteService=\" + remoteService\n            + \" unsupported due to missing table \" + TABLE_SERVICE_REMOTE_SERVICES);\n        }","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/openzipkin/zipkin/blob/878ce2a1fad54ca941d17fdcf2e1d924b148eb1f/zipkin-storage/cassandra/src/main/java/zipkin2/storage/cassandra/CassandraSpanStore.java#L165-L201","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlong endTs = 1699999999;            // seconds, not millis -> tiny window\nlong lookback = 86400000L;\nqueryRequest = QueryRequest.newBuilder().endTs(endTs).lookback(lookback).build();\n\n// after\nlong endTs = 1699999999000L;         // epoch millis\nlong lookback = 86400000L;           // one day in millis\nqueryRequest = QueryRequest.newBuilder().endTs(endTs).lookback(lookback).build();","handlingStrategy":"validation","validationCode":"long endTs = request.endTs() > 0 ? request.endTs() : System.currentTimeMillis();\nlong lookback = request.lookback() > 0 ? request.lookback() : 86400000L;\nif (lookback > endTs) lookback = endTs; // clamp to epoch so startMillis >= 0","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always use epoch milliseconds for endTs/lookback","Clamp lookback to endTs at the API boundary so the window never crosses the epoch"],"tags":["zipkin","cassandra","query","timestamp","validation"],"backgroundTag":null,"analyzedSha":"878ce2a1fad54ca941d17fdcf2e1d924b148eb1f","analyzedAt":"2026-08-14T15:17:09.895Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}