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 ledger

View on GitHub (pinned to 820761864e)

Solutions

  1. Order the endpoints before constructing the Range: use Ordering.natural() or comparePositions to pick lower/upper
  2. Return 0 or clamp the range when from > to instead of calling the API
  3. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/662f186c2012df01. Report an issue: GitHub.