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;
}
@OverrideView on GitHub (pinned to 820761864e)
Solutions
- 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.
- Generate boundaries programmatically (e.g. split 0..65535 into N equal-width segments in a loop) instead of hand-writing constants.
- 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
- Generate boundaries programmatically from segment count instead of hardcoding constants.
- Sort and normalize ranges before sending.
- Unit-test the boundary builder: assert contiguity, start at 0, end >= start.
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
- Entry-bucket boundaries must tile the 16-bit ring: last rang
- Entry-bucket 0 must span at least [0,1] to hold the canonica
- Entry-bucket subscription must declare the segment's bucket
- entryFilterNames can't be empty. To remove entry filters use
- The offloadPolicies must be specified for namespace offload.
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/99c770330802da48.
Report an issue: GitHub.