apache/pulsar · error · java.lang.IllegalArgumentException

Entry-bucket boundaries must be ascending, contiguous and st

Error message

Entry-bucket boundaries must be ascending, contiguous and start at 0: found [${start},${end}] where start ${expectedStart} was expected

What it means

Each declared boundary range must start exactly where the previous one ended plus one (contiguity), ranges must be ascending, each range must be non-empty (end >= start), and the first must start at 0. validateBucketBoundaries enforces this while converting the IntRange list into broker-side Range objects; a gap, overlap, descending order, or inverted range means entries in the skipped or duplicated bucket span would have no owner or two owners, so the subscription is rejected.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/persistent/PersistentEntryBucketDispatcherMultipleConsumers.java:89

    }

    /**
     * Validate and convert the boundaries declared at subscribe time: ascending, inclusive,
     * contiguous ranges tiling the whole 16-bit entry-bucket ring, with bucket 0 wide enough to
     * contain the canonical hash 1.
     */
    static List<Range> validateBucketBoundaries(KeySharedMeta ksm) {
        int count = ksm.getHashRangesCount();
        if (count == 0) {
            throw new IllegalArgumentException(
                    "Entry-bucket subscription must declare the segment's bucket boundaries");
        }
        List<Range> ranges = new ArrayList<>(count);
        int expectedStart = 0;
        for (int i = 0; i < count; i++) {
            IntRange r = ksm.getHashRangeAt(i);
            if (r.getStart() != expectedStart || r.getEnd() < r.getStart()) {
                throw new IllegalArgumentException("Entry-bucket boundaries must be ascending, "
                        + "contiguous and start at 0: found [" + r.getStart() + "," + r.getEnd()
                        + "] where start " + expectedStart + " was expected");
            }
            ranges.add(Range.of(r.getStart(), r.getEnd()));
            expectedStart = r.getEnd() + 1;
        }
        if (expectedStart != EntryBucketConsumerSelector.DEFAULT_RANGE_SIZE) {
            throw new IllegalArgumentException("Entry-bucket boundaries must tile the 16-bit ring: "
                    + "last range ends at " + (expectedStart - 1));
        }
        if (ranges.get(0).getEnd() < 1) {
            throw new IllegalArgumentException(
                    "Entry-bucket 0 must span at least [0,1] to hold the canonical hash");
        }
        return ranges;
    }

    @Override

View on GitHub (pinned to 820761864e)

Solutions

  1. Recompute the boundary list so range i starts at range i-1's end + 1, the first range starts at 0, and every range has end >= start.
  2. Generate boundaries programmatically (e.g. split 0..65535 into N equal-width segments in a loop) instead of hand-writing constants.
  3. Verify all consumers and the producing tooling use the identical, sorted boundary list; fix any stale client that sends a divergent list.

Example fix

// before (gap + inverted range)
ksm.addHashRange().setStart(0).setEnd(16383);
ksm.addHashRange().setStart(17000).setEnd(16999); // wrong start, end < start
// after
int segments = 4, size = 65536 / segments;
for (int i = 0; i < segments; i++) {
    ksm.addHashRange().setStart(i * size).setEnd((i + 1) * size - 1);
}
Defensive patterns

Strategy: validation

Validate before calling

static void validateRangesLocal(KeySharedMeta ksm) {
    int expectedStart = 0;
    for (int i = 0; i < ksm.getHashRangesCount(); i++) {
        var r = ksm.getHashRangeAt(i);
        if (r.getStart() != expectedStart || r.getEnd() < r.getStart()) {
            throw new IllegalArgumentException("bad range [" + r.getStart() + "," + r.getEnd()
                + "] at index " + i + ", expected start " + expectedStart);
        }
        expectedStart = r.getEnd() + 1;
    }
}

Type guard

static boolean rangesAreContiguousFromZero(KeySharedMeta ksm) {
    int expectedStart = 0;
    for (int i = 0; i < ksm.getHashRangesCount(); i++) {
        var r = ksm.getHashRangeAt(i);
        if (r.getStart() != expectedStart || r.getEnd() < r.getStart()) return false;
        expectedStart = r.getEnd() + 1;
    }
    return true;
}

Try / catch

try {
    subscribeWithEntryBuckets(ksm);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("ascending, contiguous")) {
        regenerateAndResubscribeWithComputedBoundaries();
    } else throw e;
}

Prevention

When it happens

Trigger: Calling subscribe with hashRanges that start above 0, omit a stretch of the ring (gap between range i's end+1 and range i+1's start), overlap (start < previous end+1), are listed out of ascending order, or contain start > end (inverted range).

Common situations: Hand-computed bucket boundaries with arithmetic mistakes (off-by-one between segments); boundary lists generated by buggy tooling that doesn't sort or dedupe ranges; copying a single consumer's ranges but editing one segment without adjusting the neighbors; producers/consumers built from different layout snapshots.

Related errors


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