apache/cassandra · error · IllegalStateException
Can not finish joining ring, sequence is in an incorrect sta
Error message
Can not finish joining ring, sequence is in an incorrect state. If no progress is made, cancel the join process for this node and retry
What it means
Thrown by StorageService.finishJoiningRing when a JOIN/REPLACE sequence exists but its nextStep() is not the expected MID_JOIN (for JOIN) or MID_REPLACE (for REPLACE). The sequence is mid-flight at an unexpected step, so jumping straight to finish the join would skip required state transitions. Cassandra refuses rather than corrupting cluster metadata.
Source
Thrown at src/java/org/apache/cassandra/service/StorageService.java:1100
* temporary copy of the {@link MultiStepOperation} and manually execute its next step after verifying expected
* invariants. This causes the MID step to fully execute, which then moves the sequence persisted in
* {@link ClusterMetadata}'s in-progress sequences onto the FINISH step, and we can complete the operation in the
* normal way with {@link InProgressSequences#finishInProgressSequences(MultiStepOperation.SequenceKey)}
* */
private void exitWriteSurveyMode()
{
ClusterMetadata metadata = ClusterMetadata.current();
NodeId id = metadata.myNodeId();
MultiStepOperation<?> sequence = metadata.inProgressSequences.get(id);
// Double check the conditions we verified in readyToFinishJoiningRing
if (sequence.kind() != MultiStepOperation.Kind.JOIN && sequence.kind() != MultiStepOperation.Kind.REPLACE)
throw new IllegalStateException("Can not finish joining ring as join sequence has not been started");
if ((sequence.kind() == MultiStepOperation.Kind.JOIN && sequence.nextStep() != Transformation.Kind.MID_JOIN)
|| (sequence.kind() == MultiStepOperation.Kind.REPLACE && sequence.nextStep() != Transformation.Kind.MID_REPLACE))
{
throw new IllegalStateException("Can not finish joining ring, sequence is in an incorrect state. " +
"If no progress is made, cancel the join process for this node and retry");
}
if (sequence.kind() == MultiStepOperation.Kind.REPLACE && sequence.nextStep() != Transformation.Kind.MID_REPLACE)
throw new IllegalStateException("Can not finish joining ring, sequence is in an incorrect state. " +
"If no progress is made, cancel the join process for this node and retry");
// Create a temporary new copy of the sequence with the finishJoining flag set to true and with streaming
// disabled, then execute its next step (the MID_*). We do this because effectively we want to jump over the
// MID_JOIN/MID_REPLACE of the "real" sequence. Note, this does not replace the existing sequence in
// ClusterMetadata with the temporary copy, but an effect of executing the MID step of the copy is that it will
// update the persisted state of the sequence leaving it with only the FINISH_* step to complete.
Transformation.Kind next = sequence.nextStep();
boolean success = (sequence instanceof BootstrapAndJoin)
? ((BootstrapAndJoin)sequence).finishJoiningRing().executeNext().isContinuable()
: ((BootstrapAndReplace)sequence).finishJoiningRing().executeNext().isContinuable();
if (!success)View on GitHub (pinned to 88fd0f6a0e)
Solutions
- Restart the node so in-flight operations automatically attempt to complete the next step
- Wait/monitor streaming until the sequence reaches the MID_JOIN/MID_REPLACE step, then call finishjoin again
- If no progress is made, cancel the join process for this node (nodetool cancelhandoff / cancel bootstrap) and retry the join from scratch
- Inspect ClusterMetadata (nodetool metadata) to see the actual current step before retrying
Example fix
// before: forcing finish regardless of step
storageService.finishJoiningRing();
// after: check the next step first
MultiStepOperation<?> seq = ClusterMetadata.current().inProgressSequences.get(id);
boolean ready = (seq.kind() == MultiStepOperation.Kind.JOIN && seq.nextStep() == Transformation.Kind.MID_JOIN)
|| (seq.kind() == MultiStepOperation.Kind.REPLACE && seq.nextStep() == Transformation.Kind.MID_REPLACE);
if (ready)
storageService.finishJoiningRing(); Defensive patterns
Strategy: validation
Validate before calling
MultiStepOperation<?> seq = ClusterMetadata.current().inProgressSequences.get(id);
boolean atMidStep = seq != null &&
((seq.kind() == MultiStepOperation.Kind.JOIN && seq.nextStep() == Transformation.Kind.MID_JOIN) ||
(seq.kind() == MultiStepOperation.Kind.REPLACE && seq.nextStep() == Transformation.Kind.MID_REPLACE));
if (!atMidStep) throw new IllegalStateException("Sequence not at MID_JOIN/MID_REPLACE; let in-flight operations progress first"); Type guard
static boolean isAtMidJoinStep(MultiStepOperation<?> seq) {
return (seq.kind() == MultiStepOperation.Kind.JOIN && seq.nextStep() == Transformation.Kind.MID_JOIN)
|| (seq.kind() == MultiStepOperation.Kind.REPLACE && seq.nextStep() == Transformation.Kind.MID_REPLACE);
} Try / catch
try {
storageService.finishJoiningRing();
} catch (IllegalStateException e) {
if (e.getMessage().contains("sequence is in an incorrect state")) {
logger.warn("Join sequence not at MID step; restart node or wait for in-flight ops", e);
} else throw e;
} Prevention
- Poll the sequence's nextStep() and only finish when it equals MID_JOIN/MID_REPLACE
- Prefer restarting the node and letting in-flight operations progress over manual finishjoin
- If stuck, cancel and re-run the join rather than repeatedly force-finishing
When it happens
Trigger: Calling finishJoiningRing while the in-progress JOIN sequence's nextStep() is anything other than Transformation.Kind.MID_JOIN (e.g. it is still streaming, or past MID_JOIN), or while a REPLACE sequence's nextStep() is not Kind.MID_REPLACE.
Common situations: Operator interrupts streaming then calls finishjoin too early; the join advanced past the MID step already; racing the automatic in-flight operation progress with a manual finishjoin; attempting to force-finish a replace whose MID_REPLACE step hasn't been reached.
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
- Can not resume bootstrap as join sequence has not been start
- Can not finish joining ring as join sequence has not been st
- Could not perform next step of joining the ring %s, restart
- Can't join the ring because in write_survey mode and bootstr
- Can't join the ring because bootstrap hasn't completed.
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/0b7dcbf40a233c50.
Report an issue: GitHub.