apache/pulsar · error · java.lang.IllegalArgumentException

Entry-bucket subscription must declare the segment's bucket

Error message

Entry-bucket subscription must declare the segment's bucket boundaries

What it means

An entry-bucket Key_Shared subscription requires every subscribing consumer to declare the segment's immutable bucket boundary ranges in KeySharedMeta.hashRanges. validateBucketBoundaries rejects the subscription when no ranges were declared, because without boundaries the broker cannot build the EntryBucketConsumerSelector that routes whole entries to bucket owners. It is an IllegalArgumentException thrown at dispatcher construction (or per-consumer validation in addConsumer), where it surfaces as a ConsumerAssignException subscribe failure.

Source

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

    PersistentEntryBucketDispatcherMultipleConsumers(PersistentTopic topic, ManagedCursor cursor,
            Subscription subscription, ServiceConfiguration conf, KeySharedMeta ksm) {
        // Draining is required: the inherited DrainingHashesTracker tracks the canonical bucket
        // hashes, which makes it a per-bucket handoff tracker.
        super(topic, cursor, subscription, conf, ksm,
                new EntryBucketConsumerSelector(validateBucketBoundaries(ksm)), true);
        this.bucketSelector = (EntryBucketConsumerSelector) getSelector();
    }

    /**
     * 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));
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Populate KeySharedMeta.hashRanges with ascending, contiguous IntRange segments that tile the full 16-bit ring 0..65535 (e.g. [0,16383],[16384,32767],[32768,49151],[49152,65535]).
  2. Ensure every consumer on the subscription declares the identical boundary list — the first consumer's list creates the selector and later consumers must match exactly.
  3. If using a client library, upgrade to a version that supports entry-bucket boundary declaration, or fall back to standard AUTO_SPLIT Key_Shared (drop the entryBucketDispatch flag).

Example fix

// before
KeySharedMeta ksm = new KeySharedMeta()
    .setKeySharedMode(KeySharedMode.AUTO_SPLIT)
    .setEntryBucketDispatch(true); // no hashRanges -> error
// after
KeySharedMeta ksm = new KeySharedMeta()
    .setKeySharedMode(KeySharedMode.AUTO_SPLIT)
    .setEntryBucketDispatch(true);
ksm.addHashRange().setStart(0).setEnd(16383);
ksm.addHashRange().setStart(16384).setEnd(32767);
ksm.addHashRange().setStart(32768).setEnd(49151);
ksm.addHashRange().setStart(49152).setEnd(65535);
Defensive patterns

Strategy: validation

Validate before calling

static void requireBucketBoundaries(KeySharedMeta ksm) {
    if (ksm.getHashRangesCount() == 0) {
        throw new IllegalArgumentException(
            "entryBucketDispatch requires hashRanges to be declared");
    }
}
// call before subscribing with entryBucketDispatch=true

Type guard

static boolean hasDeclaredBucketBoundaries(KeySharedMeta ksm) {
    return ksm.getHashRangesCount() > 0;
}

Prevention

When it happens

Trigger: A client subscribes with KeySharedMeta entryBucketDispatch enabled but leaves hashRanges empty (getHashRangesCount() == 0); also when a consumer joins an existing entry-bucket subscription without re-declaring boundaries, since addConsumer runs the same validation on the consumer's meta.

Common situations: Client SDKs or custom subscribe code that set the entry-bucket dispatch flag but were not updated to populate hashRanges; hand-built protobuf KeySharedMeta where the repeated hashRanges field was forgotten; older client versions predating the boundary requirement connecting to a newer broker.

Related errors


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