{"record":{"id":"52fa2f5fe9999d4e","repo":"apache/cassandra","slug":"proposed-tokens-must-be-superset-of-existing-token","errorCode":null,"errorMessage":"Proposed tokens must be superset of existing tokens","messagePattern":"Proposed tokens must be superset of existing tokens","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/ownership/UniformRangePlacement.java","lineNumber":285,"sourceCode":"        if (metadata.directory.commonSerializationVersion.isBefore(Version.V10))\n            return plan.withEndpointDeltas(directory);\n        return plan;\n    }\n\n    public DataPlacements splitRanges(TokenMap current,\n                                      TokenMap proposed,\n                                      DataPlacements currentPlacements)\n    {\n        ImmutableList<Token> currentTokens = current.tokens();\n        ImmutableList<Token> proposedTokens = proposed.tokens();\n        if (currentTokens.isEmpty() || currentTokens.equals(proposedTokens))\n        {\n            return currentPlacements;\n        }\n        else\n        {\n            if (!proposedTokens.containsAll(currentTokens))\n                throw new IllegalArgumentException(\"Proposed tokens must be superset of existing tokens\");\n            // we need to split some existing ranges, so apply the new set of tokens to the current canonical\n            // placements to get a set of placements with the proposed ranges but the current replicas\n            return splitRangesForAllPlacements(proposedTokens, currentPlacements);\n        }\n    }\n\n    @VisibleForTesting\n    DataPlacements splitRangesForAllPlacements(List<Token> proposedTokens, DataPlacements current)\n    {\n        DataPlacements.Builder builder = DataPlacements.builder(current.size());\n        current.asMap().forEach((params, placement) -> {\n            // Don't split ranges for local-only placements\n            if (params.isLocal() || params.isMeta())\n                builder.with(params, placement);\n            else\n                builder.with(params, placement.splitRangesForPlacement(proposedTokens));\n        });\n        return builder.build();","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/ownership/UniformRangePlacement.java#L267-L303","documentation":"UniformRangePlacement.splitRanges merges proposed tokens with current canonical placements. When the proposed token set does not already contain every existing token, it checks whether the proposal is a superset of the existing tokens; IllegalArgumentException is thrown when it is not, because shrinking the token set would orphan existing ranges that cannot be re-derived by splitting.","triggerScenarios":"Calling placement APIs (start/finalPlacement during bootstrap, replace, or multi-token operations) with a proposed token set that is missing at least one currently assigned token — e.g. computing placements for a subset of tokens that excludes an already-owned token.","commonSituations":"Bootstrapping only a few nodes' tokens while the placement computation passes the whole current token set incorrectly; a node restart with a changed token list dropped one of its original tokens; misconfigured initial_token after a previous bootstrap.","solutions":["Include all existing tokens in the proposed token set (proposedTokens.containsAll(currentTokens)) before invoking placement computation","Reconstruct the node's token list from cluster metadata instead of local config, and pass the union of current + new tokens","If tokens were intentionally removed, use the proper token-removal/decommission flow instead of the range-splitting path","Fix local initial_token/auto-bootstrap config so the proposal matches the metadata's current tokens"],"exampleFix":"// before\nSortedSet<Token> proposed = newTokens;\nUniformRanges after = placements.splitRanges(proposed, currentPlacements);\n// after\nSortedSet<Token> proposed = new TreeSet<>(currentTokens);\nproposed.addAll(newTokens);\nUniformRanges after = placements.splitRanges(proposed, currentPlacements);","handlingStrategy":"validation","validationCode":"if (!proposed.containsAll(currentTokens))\n    throw new IllegalArgumentException(\"proposed tokens must include all existing tokens\");","typeGuard":null,"tryCatchPattern":"try { placements.splitRanges(proposed, current); } catch (IllegalArgumentException e) { /* union proposed with currentTokens and retry */ }","preventionTips":["Always compute proposed token sets as union of existing + new tokens","Derive current tokens from ClusterMetadata, not local node config","Use dedicated flows (decommission/removenode) to shrink token sets"],"tags":["tcm","placement","token-management","precondition","illegal-argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}