{"record":{"id":"e831288f1dd6306f","repo":"apache/pulsar","slug":"ranges-for-keyshared-policy-with-overlap-between","errorCode":null,"errorMessage":"Ranges for KeyShared policy with overlap between ","messagePattern":"Ranges for KeyShared policy with overlap between ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-client-api/src/main/java/org/apache/pulsar/client/api/KeySharedPolicy.java","lineNumber":119,"sourceCode":"        public KeySharedPolicySticky ranges(Range... ranges) {\n            this.ranges.addAll(Arrays.asList(ranges));\n            return this;\n        }\n\n        @Override\n        public void validate() {\n            if (ranges.isEmpty()) {\n                throw new IllegalArgumentException(\"Ranges for KeyShared policy must not be empty.\");\n            }\n            for (int i = 0; i < ranges.size(); i++) {\n                Range range1 = ranges.get(i);\n                if (range1.getStart() < 0 || range1.getEnd() >= DEFAULT_HASH_RANGE_SIZE) {\n                    throw new IllegalArgumentException(\"Ranges must be [0, 65535] but provided range is \" + range1);\n                }\n                for (int j = 0; j < ranges.size(); j++) {\n                    Range range2 = ranges.get(j);\n                    if (i != j && range1.intersect(range2) != null) {\n                        throw new IllegalArgumentException(\"Ranges for KeyShared policy with overlap between \" + range1\n                                + \" and \" + range2);\n                    }\n                }\n            }\n        }\n\n        public List<Range> getRanges() {\n            return ranges;\n        }\n    }\n\n    /**\n     * Auto split hash range key shared policy.\n     */\n    public static class KeySharedPolicyAutoSplit extends KeySharedPolicy {\n        private static final long serialVersionUID = 1L;\n\n        KeySharedPolicyAutoSplit() {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/KeySharedPolicy.java#L101-L137","documentation":"KeySharedPolicy.KeySharedPolicySticky.validate() performs an O(n^2) pairwise check that no two sticky hash ranges intersect. If range1.intersect(range2) returns non-null for two distinct ranges, the same hash slots would be claimed by multiple consumers, so key-to-consumer assignment would be ambiguous. The policy is rejected with IllegalArgumentException naming both overlapping ranges.","triggerScenarios":"Building KeySharedPolicy.stickyRanges(Arrays.asList(Range.of(0, 100), Range.of(50, 200))) — any two ranges whose [start, end] intervals share at least one slot (e.g. touching via a common index) when the consumer attaches.","commonSituations":"Programmatically splitting the hash space with rounding errors (ceil/floor producing duplicate boundary values); copy-pasting range lists across consumers and forgetting to shrink one; merging configs where two tenants were assigned overlapping slices.","solutions":["Adjust the ranges so every slot belongs to at most one range, e.g. Range.of(0, 100) and Range.of(101, 200).","Generate ranges programmatically by partitioning 0..65534 into N equal non-overlapping slices instead of hardcoding boundaries.","Sort ranges by start and assert range[i].end < range[i+1].start before calling stickyRanges."],"exampleFix":"// before\nList<Range> ranges = Arrays.asList(Range.of(0, 100), Range.of(50, 200));\n// after\nList<Range> ranges = Arrays.asList(Range.of(0, 100), Range.of(101, 200)); // no overlap","handlingStrategy":"validation","validationCode":"static void checkNoOverlap(List<Range> ranges) {\n    List<Range> sorted = new ArrayList<>(ranges);\n    sorted.sort(Comparator.comparingInt(Range::getStart));\n    for (int i = 1; i < sorted.size(); i++) {\n        if (sorted.get(i).getStart() <= sorted.get(i - 1).getEnd())\n            throw new IllegalArgumentException(\"Overlap: \" + sorted.get(i - 1) + \" vs \" + sorted.get(i));\n    }\n}\ncheckNoOverlap(ranges); // before stickyRanges","typeGuard":"static boolean isNonOverlapping(List<Range> ranges) {\n    for (int i = 0; i < ranges.size(); i++)\n        for (int j = i + 1; j < ranges.size(); j++)\n            if (ranges.get(i).intersect(ranges.get(j)) != null) return false;\n    return true;\n}","tryCatchPattern":"try {\n    KeySharedPolicy policy = KeySharedPolicy.stickyRanges(ranges);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Ranges for KeyShared policy with overlap\")) {\n        // parse the two ranges from the message, split the shared boundary\n    } else throw e;\n}","preventionTips":["Sort ranges by start and assert range[i].end < range[i+1].start before subscribing.","Split the hash space with code (e.g. 65535 / N slices) rather than manual numbers.","Keep per-consumer range lists in one shared config so assignments cannot drift apart."],"tags":["keyshared","hash-range","illegal-argument","overlap"],"backgroundTag":"hash-range-overlap","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}