apache/cassandra · error · RuntimeException

Bad NodeState

Error message

Bad NodeState 

What it means

GossipHelper.nodeStateToStatus converts legacy gossip NodeState values into VersionedValue statuses during gossip-to-TCM upgrade; the default branch throws RuntimeException because a NodeState outside the handled set cannot be represented, indicating an unanticipated state during upgrade.

Source

Thrown at src/java/org/apache/cassandra/tcm/compatibility/GossipHelper.java:186

                break;
            case MOVING:
                sequence = metadata.inProgressSequences.get(nodeId);
                if (!(sequence instanceof Move))
                {
                    logger.error(String.format("Cannot construct gossip state. Node is in %s state, but sequence the is %s", NodeState.MOVING, sequence));
                    return null;
                }
                Collection<Token> moveTokens = getTokensFromOperation(sequence);
                if (!moveTokens.isEmpty())
                {
                    Token token = ((Move) sequence).tokens.iterator().next();
                    status = valueFactory.moving(token);
                }
                break;
            case REGISTERED:
                break;
            default:
                throw new RuntimeException("Bad NodeState " + nodeState);
        }
        return status;
    }

    public static Collection<Token> getTokensFromOperation(NodeId nodeId, ClusterMetadata metadata)
    {
        return getTokensFromOperation(metadata.inProgressSequences.get(nodeId));
    }

    public static Collection<Token> getTokensFromOperation(MultiStepOperation<?> sequence)
    {
        if (null == sequence)
            return Collections.emptySet();

        if (sequence.kind() == MultiStepOperation.Kind.JOIN)
            return new HashSet<>(((BootstrapAndJoin)sequence).finishJoin.tokens);
        else if (sequence.kind() == MultiStepOperation.Kind.REPLACE)
            return new HashSet<>(((BootstrapAndReplace)sequence).bootstrapTokens);

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Upgrade all nodes so every member understands each other's NodeState values
  2. Check which node/state triggered it in logs; if it's a leftover dead node, remove it from the cluster before the TCM migration
  3. Ensure the pre-upgrade Cassandra version is a supported migration baseline
  4. Report/patch: extend nodeStateToStatus with the missing NodeState case if it's a legitimate state

Example fix

// before
default:
    throw new RuntimeException("Bad NodeState " + nodeState);
// after
case UNKNOWN_NEW_STATE: // e.g. added in a later version
    status = valueFactory.normal(Collections.emptySet());
    break;
default:
    throw new RuntimeException("Bad NodeState " + nodeState);
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-upgrade check
for (EndpointState es : gossipStates)
    assert knownNodeStates.contains(es.getNodeState()) : "unknown NodeState during upgrade";

Try / catch

try { GossipHelper.nodeStateToStatus(nodeState, valueFactory); }
catch (RuntimeException e) {
    if (e.getMessage().startsWith("Bad NodeState")) { /* upgrade version or remove stale node */ }
    else throw e;
}

Prevention

When it happens

Trigger: Running the gossip compatibility/upgrade path with an endpoint whose NodeState is not one of the explicitly handled values (e.g. LEFT-ish, future/unknown enum value) when translating to a gossip status.

Common situations: Mixed-version upgrade where a newer node advertises a NodeState this node's code doesn't know; corrupted endpoint state; attempting upgrade with an unsupported intermediate version.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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