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

  1. Restart the node so in-flight operations automatically attempt to complete the next step
  2. Wait/monitor streaming until the sequence reaches the MID_JOIN/MID_REPLACE step, then call finishjoin again
  3. If no progress is made, cancel the join process for this node (nodetool cancelhandoff / cancel bootstrap) and retry the join from scratch
  4. 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

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


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