apache/cassandra · error · IllegalArgumentException

Ranges supplied to SSTableCursorReader must be…

Error message

Ranges supplied to SSTableCursorReader must be non-overlapping and in ascending order.

What it means

SSTableCursorReader reads a sequence of byte-range segments from an sstable and requires them strictly ordered. advanceSegment() detects that the next range starts before the current segment's end (overlap or descending order) and throws IllegalArgumentException.

Solutions

  1. Sort the ranges by lowerPosition before constructing the reader.
  2. Coalesce overlapping/adjacent ranges before passing them in.
  3. Pre-validate the segments array with a loop and fail fast with a clearer error.
  4. Fix the producer of the bounds so it emits non-overlapping ascending ranges.

Example fix

// before
SSTableCursorReader reader = new SSTableCursorReader(..., segments);
// after
Arrays.sort(segments, Comparator.comparingLong(PartitionPositionBounds::lowerPosition));
for (int i = 1; i < segments.length; i++)
    if (segments[i-1].upperPosition > segments[i].lowerPosition)
        throw new IllegalArgumentException("overlapping range at index " + i);
SSTableCursorReader reader = new SSTableCursorReader(..., segments);
Defensive patterns

Strategy: validation

Validate before calling

for (int i = 1; i < segments.length; i++) {
    if (segments[i].lowerPosition < segments[i-1].upperPosition)
        throw new IllegalArgumentException("overlapping/out-of-order range at " + i);
    if (segments[i].upperPosition < segments[i].lowerPosition)
        throw new IllegalArgumentException("inverted range at " + i);
}

Try / catch

try {
    reader = new SSTableCursorReader(..., segments);
} catch (IllegalArgumentException e) {
    // re-sort/coalesce ranges and retry once
}

Prevention

When it happens

Trigger: Constructing SSTableCursorReader with a segments array where an earlier range's upperPosition exceeds a later range's lowerPosition.

Common situations: Custom tooling building PartitionPositionBounds lists without sorting; off-by-one errors when slicing an sstable for parallel scanning; merging ranges from multiple sources unsorted.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/856e62baf4533529. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/io/sstable/SSTableCursorReader.java:679

     */
    private int afterPartitionEnd()
    {
        return dataReader.getPosition() < segmentEnd ? PARTITION_START : advanceSegment();
    }

    /**
     * Enters the next segment that has bytes, as {@code SSTableSimpleScanner.advanceRange} does,
     * and leaves the reader at its first partition.
     *
     * @return PARTITION_START, or DONE when no segment is left
     */
    private int advanceSegment()
    {
        while (segmentIndex < segments.length)
        {
            PartitionPositionBounds next = segments[segmentIndex++];
            if (segmentEnd > next.lowerPosition)
                throw new IllegalArgumentException("Ranges supplied to SSTableCursorReader must be non-overlapping and in ascending order.");
            if (next.upperPosition < next.lowerPosition)
                throw new IllegalArgumentException("A range supplied to SSTableCursorReader ends before it starts: "
                                                   + next.lowerPosition + " > " + next.upperPosition);
            // An empty range carries no partition. Skip it WITHOUT touching segmentStart, segmentEnd
            // or the byte accounting: bytesRead() is bytesReadInPreviousSegments plus the progress
            // through the current segment, so moving those to a range the reader never visits makes
            // the count go backwards. The scanner avoids this by seeking to the empty range's start;
            // not seeking is cheaper and reads nothing outside a range this cursor covers.
            if (next.lowerPosition == next.upperPosition)
                continue;

            bytesReadInPreviousSegments += segmentEnd - segmentStart;
            segmentStart = next.lowerPosition;
            segmentEnd = next.upperPosition;
            try
            {
                seekPartition(segmentStart);
            }

View on GitHub (pinned to 88fd0f6a0e)