apache/cassandra · error · ProtocolException

Unexpected message STARTUP, the connection is already…

Error message

Unexpected message STARTUP, the connection is already initialized

What it means

A ProtocolException thrown when a client sends a second STARTUP frame on a connection that already completed initialization (stage READY). The CQL binary protocol allows STARTUP only once per connection; re-initialization requires a new connection. The server rejects the duplicate STARTUP while still allowing other messages in the READY state.

Solutions

  1. Reuse the connection without resending STARTUP; create a brand-new connection if re-initialization is needed
  2. Fix reconnect/retry logic to establish a fresh socket instead of replaying the handshake
  3. Don't try to change compression/options mid-connection — set them in the initial STARTUP only
  4. Check driver version for known bugs re-sending STARTUP on recovery

Example fix

// before (re-handshake on same connection after error)
connection.send(startup); // already READY
// after
connection.close();
Connection fresh = factory.newConnection();
fresh.send(startup);
Defensive patterns

Strategy: type-guard

Validate before calling

if (connection.isReady() && message instanceof StartupMessage)
    throw new IllegalStateException("STARTUP is only allowed once per connection; open a new connection instead");

Type guard

boolean maySendStartup(Connection c) { return c.state() == ConnectionState.FRESH || c.state() == ConnectionState.ESTABLISHED; }

Try / catch

try { connection.send(startup); } catch (ProtocolException e) { if (e.getMessage().contains("already initialized")) { connection.close(); connection = openNewConnection(); } else throw e; }

Prevention

When it happens

Trigger: Client sends STARTUP again after the connection is READY (already did STARTUP, optional auth, and is serving queries); validateNewMessage() in READY stage sees type == STARTUP and throws.

Common situations: Client reconnect logic reusing the same connection object and re-sending STARTUP, driver bug after a session re-init, connection wrappers that reset options by re-running the handshake, or hand-rolled clients toggling compression via a new STARTUP.

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

Appendix: source

Thrown at src/java/org/apache/cassandra/transport/ServerConnection.java:94

        return stage;
    }

    QueryState validateNewMessage(Message.Type type, ProtocolVersion version)
    {
        switch (stage)
        {
            case ESTABLISHED:
                if (type != Message.Type.STARTUP && type != Message.Type.OPTIONS)
                    throw new ProtocolException(String.format("Unexpected message %s, expecting STARTUP or OPTIONS", type));
                break;
            case AUTHENTICATING:
                // Support both SASL auth from protocol v2 and the older style Credentials auth from v1
                if (type != Message.Type.AUTH_RESPONSE && type != Message.Type.CREDENTIALS)
                    throw new ProtocolException(String.format("Unexpected message %s, expecting %s", type, version == ProtocolVersion.V1 ? "CREDENTIALS" : "SASL_RESPONSE"));
                break;
            case READY:
                if (type == Message.Type.STARTUP)
                    throw new ProtocolException("Unexpected message STARTUP, the connection is already initialized");
                break;
            default:
                throw new AssertionError();
        }

        return new QueryState(clientState);
    }

    void applyStateTransition(Message.Type requestType, Message.Type responseType)
    {
        switch (stage)
        {
            case ESTABLISHED:
                if (requestType == Message.Type.STARTUP)
                {
                    if (responseType == Message.Type.AUTHENTICATE)
                        stage = ConnectionStage.AUTHENTICATING;
                    else if (responseType == Message.Type.READY)

View on GitHub (pinned to 88fd0f6a0e)