{"record":{"id":"542816d679f3e378","repo":"apache/pulsar","slug":"entry-bucket-subscription-must-declare-the-segment","errorCode":null,"errorMessage":"Entry-bucket subscription must declare the segment's bucket boundaries","messagePattern":"Entry-bucket subscription must declare the segment's bucket boundaries","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/service/persistent/PersistentEntryBucketDispatcherMultipleConsumers.java","lineNumber":81,"sourceCode":"\n    PersistentEntryBucketDispatcherMultipleConsumers(PersistentTopic topic, ManagedCursor cursor,\n            Subscription subscription, ServiceConfiguration conf, KeySharedMeta ksm) {\n        // Draining is required: the inherited DrainingHashesTracker tracks the canonical bucket\n        // hashes, which makes it a per-bucket handoff tracker.\n        super(topic, cursor, subscription, conf, ksm,\n                new EntryBucketConsumerSelector(validateBucketBoundaries(ksm)), true);\n        this.bucketSelector = (EntryBucketConsumerSelector) getSelector();\n    }\n\n    /**\n     * Validate and convert the boundaries declared at subscribe time: ascending, inclusive,\n     * contiguous ranges tiling the whole 16-bit entry-bucket ring, with bucket 0 wide enough to\n     * contain the canonical hash 1.\n     */\n    static List<Range> validateBucketBoundaries(KeySharedMeta ksm) {\n        int count = ksm.getHashRangesCount();\n        if (count == 0) {\n            throw new IllegalArgumentException(\n                    \"Entry-bucket subscription must declare the segment's bucket boundaries\");\n        }\n        List<Range> ranges = new ArrayList<>(count);\n        int expectedStart = 0;\n        for (int i = 0; i < count; i++) {\n            IntRange r = ksm.getHashRangeAt(i);\n            if (r.getStart() != expectedStart || r.getEnd() < r.getStart()) {\n                throw new IllegalArgumentException(\"Entry-bucket boundaries must be ascending, \"\n                        + \"contiguous and start at 0: found [\" + r.getStart() + \",\" + r.getEnd()\n                        + \"] where start \" + expectedStart + \" was expected\");\n            }\n            ranges.add(Range.of(r.getStart(), r.getEnd()));\n            expectedStart = r.getEnd() + 1;\n        }\n        if (expectedStart != EntryBucketConsumerSelector.DEFAULT_RANGE_SIZE) {\n            throw new IllegalArgumentException(\"Entry-bucket boundaries must tile the 16-bit ring: \"\n                    + \"last range ends at \" + (expectedStart - 1));\n        }","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/persistent/PersistentEntryBucketDispatcherMultipleConsumers.java#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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]).","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.","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)."],"exampleFix":"// before\nKeySharedMeta ksm = new KeySharedMeta()\n    .setKeySharedMode(KeySharedMode.AUTO_SPLIT)\n    .setEntryBucketDispatch(true); // no hashRanges -> error\n// after\nKeySharedMeta ksm = new KeySharedMeta()\n    .setKeySharedMode(KeySharedMode.AUTO_SPLIT)\n    .setEntryBucketDispatch(true);\nksm.addHashRange().setStart(0).setEnd(16383);\nksm.addHashRange().setStart(16384).setEnd(32767);\nksm.addHashRange().setStart(32768).setEnd(49151);\nksm.addHashRange().setStart(49152).setEnd(65535);","handlingStrategy":"validation","validationCode":"static void requireBucketBoundaries(KeySharedMeta ksm) {\n    if (ksm.getHashRangesCount() == 0) {\n        throw new IllegalArgumentException(\n            \"entryBucketDispatch requires hashRanges to be declared\");\n    }\n}\n// call before subscribing with entryBucketDispatch=true","typeGuard":"static boolean hasDeclaredBucketBoundaries(KeySharedMeta ksm) {\n    return ksm.getHashRangesCount() > 0;\n}","tryCatchPattern":null,"preventionTips":["Always build entry-bucket boundaries via a shared helper/constant so all consumers send the identical list.","Add a client-side assert that hashRangesCount > 0 whenever entryBucketDispatch is set.","Pin client and broker versions so the entry-bucket flag is only used where fully supported."],"tags":["pulsar","key-shared","entry-bucket","subscription-config"],"backgroundTag":"missing-hash-ranges","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}