apache/cassandra · error · ProtocolException

Unexpected message , expecting STARTUP or OPTIONS

Error message

Unexpected message %s, expecting STARTUP or OPTIONS

What it means

A protocol-level (ProtocolException) error sent when a message arrives on a connection that has not yet completed initialization, and the message type is neither STARTUP nor OPTIONS. A new CQL connection must begin with STARTUP (or OPTIONS for negotiation); anything else is out of sequence and the connection is rejected. This enforces the CQL binary protocol state machine on the server.

Solutions

  1. Ensure the client always sends a STARTUP frame (with CQL version options) before any other request
  2. Fix connection-reuse logic so pooled connections are re-handshaken after reconnect
  3. Check proxies/load balancers for frame replay or misrouted streams
  4. If scanning/fuzzing traffic, use the correct protocol handshake or the CQL test harness

Example fix

// before (client sends query immediately)
connection.send(new QueryMessage("SELECT ...", ...));
// after
connection.send(new StartupMessage(options));
connection.send(new QueryMessage("SELECT ...", ...));
Defensive patterns

Strategy: type-guard

Validate before calling

if (connection.isFresh() && messageType != STARTUP && messageType != OPTIONS)
    throw new IllegalStateException("first message on a new connection must be STARTUP or OPTIONS");

Type guard

boolean validFirstMessage(Message.Type t) { return t == Message.Type.STARTUP || t == Message.Type.OPTIONS; }

Try / catch

try { connection.sendMessage(msg); } catch (DriverException e) { if (isProtocolExceptionContaining(e, "expecting STARTUP or OPTIONS")) reconnectAndHandshake(); else throw e; }

Prevention

When it happens

Trigger: Client sends QUERY, REGISTER, or any non-STARTUP/OPTIONS frame as the first message on a fresh connection; ServerConnection.validateNewMessage() is in stage ESTABLISHED (pre-handshake) and the type check fails.

Common situations: Hand-rolled or buggy clients skipping STARTUP, connection pooling reusing a socket whose handshake was lost, proxies replaying buffered frames onto a new connection, or fuzzers/scanners hitting the native port.

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

Appendix: source

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

    }

    public long requestCount()
    {
        return requests;
    }

    ConnectionStage stage()
    {
        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)

View on GitHub (pinned to 88fd0f6a0e)