apache/cassandra · info
Token changing ownership from to
Error message
Token {} changing ownership from {} to {} What it means
LegacyStateListener.logCompletedReplacement warns, for each token a replacement sequence moves, that ownership of that token is changing from the replaced node to the replacement node. It is an informational migration log emitted while applying TCM sequences to legacy local/remote state (processChangesToLocalState / processChangesToRemotePeers callers).
Solutions
- If intended, no action: this confirms token ranges moved to the replacement node; run repair afterwards.
- Verify streaming finished and ranges are consistent (nodetool netsso/repair) after replacement.
- If replacement was accidental, check the replace_address config and correct node identity before further bootstraps.
- Monitor pending ranges drain so clients stop routing to the replaced node.
Defensive patterns
Strategy: try-catch
Try / catch
// This is an informational log during replacement; guard the replacement itself
try {
startReplacement(replaced, replacement);
} catch (Exception e) {
logger.error("Replacement initiation failed; tokens not moved", e);
} Prevention
- Double-check cassandra.replace_address before restart.
- Run repair after every replacement to re-sync moved token ranges.
- Monitor this log to confirm only intended tokens moved.
When it happens
Trigger: A StartReplace/sequence completes via LegacyStateListener.processChanges when a node bootstraps with the 'replace' option: directory.endpoint(sequence.startReplace.replacement()) resolves the replacement, GossipHelper.getTokensFromOperation yields the moved tokens, and each token's ownership change is logged.
Common situations: Replacing a dead node with cassandra.replace_address; operator-driven replacement during hardware failover; accidental replacements triggered with wrong replace_address should be checked if token ownership shifts unexpectedly.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Booting with ClusterMetadata from file:
- Can't revert join from
- Cleaning up orphaned PreInitialize at epoch
- Expected to complete startup sequence, but did not. Can't…
- Failed to insert pre-initialize entry into distributed…
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/d6b3c8d9fdedee81.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/tcm/listeners/LegacyStateListener.java:245
{
for (InetAddressAndPort remove : removed)
{
GossipHelper.removeAndEvict(remove);
PeersTable.removeFromSystemPeersTables(remove);
}
}
private void logCompletedReplacement(Directory directory, BootstrapAndReplace sequence)
{
// legacy log message for compatibility (& tests)
InetAddressAndPort replaced = directory.endpoint(sequence.startReplace.replaced());
InetAddressAndPort replacement = directory.endpoint(sequence.startReplace.replacement());
Collection<Token> tokens = GossipHelper.getTokensFromOperation(sequence);
logger.info("Node {} will complete replacement of {} for tokens {}", replacement, replaced, tokens);
if (!replacement.equals(replaced))
{
for (Token token : tokens)
logger.warn("Token {} changing ownership from {} to {}", token, replaced, replacement);
}
}
private boolean directoryEntryChangedFor(NodeId nodeId, Directory prev, Directory next)
{
return prev.peerState(nodeId) != next.peerState(nodeId) ||
!Objects.equals(prev.getNodeAddresses(nodeId), next.getNodeAddresses(nodeId)) ||
!Objects.equals(prev.version(nodeId), next.version(nodeId)) ||
!Objects.equals(prev.location(nodeId), next.location(nodeId));
}
}
View on GitHub (pinned to 88fd0f6a0e)