apache/cassandra · error · IllegalArgumentException
A range supplied to SSTableCursorReader ends before it…
Error message
A range supplied to SSTableCursorReader ends before it starts: ${lowerPosition} > ${upperPosition} What it means
SSTableCursorReader validates each byte-range segment as it advances: a PartitionPositionBounds whose upperPosition is smaller than its lowerPosition is nonsensical. The reader throws IllegalArgumentException naming both positions rather than producing garbage reads.
Solutions
- Fix the code computing upperPosition so it is always >= lowerPosition.
- Filter out or normalize inverted/empty ranges before constructing the reader.
- Pre-validate all PartitionPositionBounds before passing them to the reader.
- If bounds come from persisted metadata, regenerate them by rescanning the sstable.
Example fix
// before
SSTableCursorReader reader = new SSTableCursorReader(..., rawSegments);
// after
List<PartitionPositionBounds> valid = rawSegments.stream()
.filter(b -> b.upperPosition >= b.lowerPosition)
.collect(Collectors.toList());
SSTableCursorReader reader = new SSTableCursorReader(..., valid.toArray(new PartitionPositionBounds[0])); Defensive patterns
Strategy: validation
Validate before calling
Arrays.stream(bounds)
.filter(b -> b.upperPosition < b.lowerPosition)
.findAny()
.ifPresent(b -> { throw new IllegalArgumentException("inverted bound " + b); }); Try / catch
try {
reader = new SSTableCursorReader(..., bounds);
} catch (IllegalArgumentException e) {
// sanitize bounds (drop/fix inverted entries) and retry
} Prevention
- Compute end offsets as start + measured length, never independently.
- Filter empty/inverted ranges at the producer.
- Assert bounds invariants wherever PartitionPositionBounds values are generated.
When it happens
Trigger: Constructing SSTableCursorReader with a segments array containing an inverted PartitionPositionBounds (lowerPosition > upperPosition), typically produced by buggy bound computation.
Common situations: Buggy custom range slicing of sstables; deserializing corrupted partition bounds metadata; arithmetic errors computing end offsets (e.g. wrong length added to start).
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
- Ranges supplied to SSTableCursorReader must be…
- Invalid negative chunk index
- The requested position exceeds the index length
- The requested position exceeds the index length
- 3
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/d6e9ffdf7bb8f87d.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/io/sstable/SSTableCursorReader.java:681
{
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);
}
catch (IOException e)
{View on GitHub (pinned to 88fd0f6a0e)