apache/pulsar · error · IllegalArgumentException
Invalid range ${range}
Error message
Invalid range ${range} What it means
ManagedLedgerImpl.getNumberOfEntries(Range<Position>) validates the range after comparing endpoints; if fromPosition is greater than toPosition, the range is invalid and IllegalArgumentException is thrown. The library requires lower-to-upper ordered, correctly bounded ranges.
Source
Thrown at managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java:4053
}
/**
* Get the number of entries between a contiguous range of two positions.
*
* @param range
* the position range
* @return the count of entries
*/
public long getNumberOfEntries(Range<Position> range) {
Position fromPosition = range.lowerEndpoint();
boolean fromIncluded = range.lowerBoundType() == BoundType.CLOSED;
Position toPosition = range.upperEndpoint();
boolean toIncluded = range.upperBoundType() == BoundType.CLOSED;
if (comparePositions(fromPosition, toPosition) > 0) {
log.warn().attr("fromPosition", fromPosition)
.attr("toPosition", toPosition)
.log("Getting number of entries with an invalid range");
throw new IllegalArgumentException("Invalid range " + range);
}
// 1. If the "fromPosition" is after "toPosition", then there is no entry in the range.
// 2. If both "formPosition" and "toPosition" have negative entry id amd in the same ledger, then there is no
// entry in the range.
if (fromPosition.getLedgerId() > toPosition.getLedgerId()
|| (fromPosition.getLedgerId() == toPosition.getLedgerId()
&& fromPosition.getEntryId() > toPosition.getEntryId())
|| (fromPosition.getLedgerId() == toPosition.getLedgerId()
&& fromPosition.getEntryId() < 0 && toPosition.getEntryId() < 0)) {
return 0;
}
// If the 2 positions are in the same ledger.
if (fromPosition.getLedgerId() == toPosition.getLedgerId()) {
LedgerInfo li = ledgers.get(toPosition.getLedgerId());
if (li != null) {
// If the 2 positions are in the same ledgerView on GitHub (pinned to 820761864e)
Solutions
- Order the endpoints before constructing the Range: use Ordering.natural() or comparePositions to pick lower/upper
- Return 0 or clamp the range when from > to instead of calling the API
- Verify the position source (cursor state) — a rewind can invert expected order
Example fix
// before
Range<Position> r = Range.closed(readPos, markDeletePos); // may be reversed
// after
Range<Position> r = fromPos.compareTo(toPos) <= 0
? Range.closed(fromPos, toPos)
: Range.closed(toPos, fromPos); Defensive patterns
Strategy: validation
Validate before calling
if (fromPos.compareTo(toPos) > 0) {
Position tmp = fromPos; fromPos = toPos; toPos = tmp; // normalize order
}
Range<Position> range = Range.closed(fromPos, toPos); Try / catch
try {
return ml.getNumberOfEntries(range);
} catch (IllegalArgumentException e) {
log.warn("invalid entry range: {}", range, e);
return 0L;
} Prevention
- Normalize endpoint order before building the Range
- Account for cursor rewinds that invert position order
- Use Range.closed/closedOpen consistently
- Clamp ranges to the ledger's [first, last] positions
When it happens
Trigger: Calling getNumberOfEntries with a Range whose lower endpoint is after its upper endpoint (e.g. from > to because cursor positions were computed in reverse), or building Range directly from mark-delete/read positions without ordering them first.
Common situations: Stats/monitoring code computing backlog over a reversed range after cursor rewinds; comparing positions across ledgers incorrectly; using Guava Range with swapped endpoints.
Related errors
- Range end must >= range start.
- Positions must not be null
- Split service unit should be split into 2 service units.
- ResourceGroupCreate: Invalid null ResourceGroup config
- ResourceGroupCreate: can't create resource group with an emp
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/662f186c2012df01.
Report an issue: GitHub.