apache/cassandra · error · IllegalArgumentException

Proposed tokens must be superset of existing tokens

Error message

Proposed tokens must be superset of existing tokens

What it means

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.

Solutions

  1. Include all existing tokens in the proposed token set (proposedTokens.containsAll(currentTokens)) before invoking placement computation
  2. Reconstruct the node's token list from cluster metadata instead of local config, and pass the union of current + new tokens
  3. If tokens were intentionally removed, use the proper token-removal/decommission flow instead of the range-splitting path
  4. Fix local initial_token/auto-bootstrap config so the proposal matches the metadata's current tokens

Example fix

// before
SortedSet<Token> proposed = newTokens;
UniformRanges after = placements.splitRanges(proposed, currentPlacements);
// after
SortedSet<Token> proposed = new TreeSet<>(currentTokens);
proposed.addAll(newTokens);
UniformRanges after = placements.splitRanges(proposed, currentPlacements);
Defensive patterns

Strategy: validation

Validate before calling

if (!proposed.containsAll(currentTokens))
    throw new IllegalArgumentException("proposed tokens must include all existing tokens");

Try / catch

try { placements.splitRanges(proposed, current); } catch (IllegalArgumentException e) { /* union proposed with currentTokens and retry */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/52fa2f5fe9999d4e. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/tcm/ownership/UniformRangePlacement.java:285

        if (metadata.directory.commonSerializationVersion.isBefore(Version.V10))
            return plan.withEndpointDeltas(directory);
        return plan;
    }

    public DataPlacements splitRanges(TokenMap current,
                                      TokenMap proposed,
                                      DataPlacements currentPlacements)
    {
        ImmutableList<Token> currentTokens = current.tokens();
        ImmutableList<Token> proposedTokens = proposed.tokens();
        if (currentTokens.isEmpty() || currentTokens.equals(proposedTokens))
        {
            return currentPlacements;
        }
        else
        {
            if (!proposedTokens.containsAll(currentTokens))
                throw new IllegalArgumentException("Proposed tokens must be superset of existing tokens");
            // we need to split some existing ranges, so apply the new set of tokens to the current canonical
            // placements to get a set of placements with the proposed ranges but the current replicas
            return splitRangesForAllPlacements(proposedTokens, currentPlacements);
        }
    }

    @VisibleForTesting
    DataPlacements splitRangesForAllPlacements(List<Token> proposedTokens, DataPlacements current)
    {
        DataPlacements.Builder builder = DataPlacements.builder(current.size());
        current.asMap().forEach((params, placement) -> {
            // Don't split ranges for local-only placements
            if (params.isLocal() || params.isMeta())
                builder.with(params, placement);
            else
                builder.with(params, placement.splitRangesForPlacement(proposedTokens));
        });
        return builder.build();

View on GitHub (pinned to 88fd0f6a0e)