apache/cassandra · error · IllegalStateException

Cannot move to UNREGISTERED state (%s)

Error message

Cannot move to UNREGISTERED state (%s)

What it means

RegistrationStatus.onInitialized() atomically moves the node's registration state machine from INITIAL to UNREGISTERED using compareAndSet. If the current state is anything other than INITIAL (e.g. already UNREGISTERED, REGISTERED, or REGISTERING) the CAS fails and this IllegalStateException is thrown. It indicates the initialization callback fired twice or the state advanced concurrently, so the expected lifecycle transition is invalid.

Source

Thrown at src/java/org/apache/cassandra/tcm/RegistrationStatus.java:53

    public static final RegistrationStatus instance = new RegistrationStatus();
    private final AtomicReference<RegistrationStatus.State> state = new AtomicReference<>(State.INITIAL);

    public RegistrationStatus.State getCurrent()
    {
        return state.get();
    }

    @VisibleForTesting
    public void resetState()
    {
        state.set(State.INITIAL);
    }

    public void onInitialized()
    {
        logger.info("Node is initialized, moving to UNREGISTERED state");
        if (!state.compareAndSet(State.INITIAL, State.UNREGISTERED))
            throw new IllegalStateException(String.format("Cannot move to UNREGISTERED state (%s)", state.get()));
    }

    public void onRegistration()
    {
        // This may have been done already if the metadata log replay at start up included our registration
        RegistrationStatus.State current = state.get();
        if (current == State.REGISTERED)
            return;

        logger.info("This node is registered, moving state to REGISTERED and interrupting any previously established peer connections");
        state.getAndSet(RegistrationStatus.State.REGISTERED);
        MessagingService.instance().channelManagers.keySet().forEach(MessagingService.instance()::interruptOutbound);
    }
}

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Log/inspect the current state at the throw site to determine which transition already ran.
  2. Ensure onInitialized() is invoked exactly once per process startup.
  3. If the registration was already replayed from the log, use onRegistration()'s idempotent path rather than forcing onInitialized().
  4. In tests, create a fresh RegistrationStatus per test instead of reusing an instance across startup attempts.

Example fix

// before: unconditional callback during double startup
registrationStatus.onInitialized();
// after: guard on the expected state
if (registrationStatus.state() == RegistrationStatus.State.INITIAL)
    registrationStatus.onInitialized();
Defensive patterns

Strategy: type-guard

Validate before calling

if (status.state() != RegistrationStatus.State.INITIAL) {
    // skip onInitialized; state already advanced
}

Type guard

boolean canInitialize(RegistrationStatus s) { return s.state() == RegistrationStatus.State.INITIAL; }

Try / catch

try { status.onInitialized(); }
catch (IllegalStateException e) {
    if (e.getMessage().startsWith("Cannot move to UNREGISTERED")) {
        logger.info("Registration already advanced past INITIAL: {}", e.getMessage());
    } else throw e;
}

Prevention

When it happens

Trigger: onInitialized() called when the state machine is no longer in State.INITIAL; concurrent/duplicate invocation of the initialization hook; replayed startup path already advanced the state to UNREGISTERED or REGISTERED before onInitialized ran.

Common situations: Double startup or repeated TCM initialization in the same JVM (common in tests); a race between startup hooks; replay of the metadata log containing the node's registration before the initialization callback.

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