apache/cassandra · error · IllegalStateException

Can not finish joining ring as join sequence has not been st

Error message

Can not finish joining ring as join sequence has not been started

What it means

Thrown by StorageService.finishJoiningRing when the node's in-progress sequence kind is neither JOIN nor REPLACE, meaning no join/replace sequence was started even though the operator asked to finish joining the ring. This is the double-check of the conditions verified earlier in readyToFinishJoiningRing. It prevents finishing a join that does not exist in ClusterMetadata.

Source

Thrown at src/java/org/apache/cassandra/service/StorageService.java:1095

     * At the point when an operator decides to bring the node out of write survey mode, we need to execute the
     * remaining steps of the join/replace sequence. The caveat is that although this execution will recommence at the
     * point it left off (after the START_JOIN/START_REPLACE step), the {@code finishJoiningRing} flag which causes
     * execution to pause for write survey mode must be overridden, so that we fully execute the MID step. We also want
     * force the {@code streamData} flag to false, to prevent re-streaming the bootstrap data. To do this, we create a
     * 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();

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Confirm with nodetool metadata (or logs) that a JOIN or REPLACE sequence is actually in progress for this node
  2. If no join exists, bootstrap/join the node normally instead of calling finishjoin
  3. If the join already completed, no action needed — the node is a ring member
  4. If the wrong operation is in progress, cancel it and restart the intended join process
Defensive patterns

Strategy: validation

Validate before calling

MultiStepOperation<?> seq = ClusterMetadata.current().inProgressSequences.get(id);
boolean canFinish = seq != null && (seq.kind() == MultiStepOperation.Kind.JOIN || seq.kind() == MultiStepOperation.Kind.REPLACE);
if (!canFinish) throw new IllegalStateException("No JOIN/REPLACE sequence in progress; cannot finish joining ring");

Type guard

static boolean isJoinOrReplace(MultiStepOperation<?> seq) {
    return seq != null && (seq.kind() == MultiStepOperation.Kind.JOIN || seq.kind() == MultiStepOperation.Kind.REPLACE);
}

Try / catch

try {
    storageService.finishJoiningRing();
} catch (IllegalStateException e) {
    if (e.getMessage().contains("join sequence has not been started")) {
        logger.warn("No join/replace sequence in progress; nothing to finish", e);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling nodetool finishjoin / finishJoiningRing when metadata.inProgressSequences for this node holds a sequence whose kind() is not MultiStepOperation.Kind.JOIN or Kind.REPLACE — e.g. no sequence at all, or an unrelated sequence (bootstrap-only, decommission, rebuild).

Common situations: Running finishjoin on a node that was never bootstrapped into the ring; running it after the join already finished; running it on a node executing a different multi-step operation; confusion between resumebootstrap and finishjoin commands.

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/e58d7e42a1cfe213. Report an issue: GitHub.