{"record":{"id":"16f57f99498374b7","repo":"apache/cassandra","slug":"unexpected-message-s-expecting-startup-or-option","errorCode":null,"errorMessage":"Unexpected message %s, expecting STARTUP or OPTIONS","messagePattern":"Unexpected message (.+?), expecting STARTUP or OPTIONS","errorType":"exception","errorClass":"ProtocolException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/transport/ServerConnection.java","lineNumber":85,"sourceCode":"    }\n\n    public long requestCount()\n    {\n        return requests;\n    }\n\n    ConnectionStage stage()\n    {\n        return stage;\n    }\n\n    QueryState validateNewMessage(Message.Type type, ProtocolVersion version)\n    {\n        switch (stage)\n        {\n            case ESTABLISHED:\n                if (type != Message.Type.STARTUP && type != Message.Type.OPTIONS)\n                    throw new ProtocolException(String.format(\"Unexpected message %s, expecting STARTUP or OPTIONS\", type));\n                break;\n            case AUTHENTICATING:\n                // Support both SASL auth from protocol v2 and the older style Credentials auth from v1\n                if (type != Message.Type.AUTH_RESPONSE && type != Message.Type.CREDENTIALS)\n                    throw new ProtocolException(String.format(\"Unexpected message %s, expecting %s\", type, version == ProtocolVersion.V1 ? \"CREDENTIALS\" : \"SASL_RESPONSE\"));\n                break;\n            case READY:\n                if (type == Message.Type.STARTUP)\n                    throw new ProtocolException(\"Unexpected message STARTUP, the connection is already initialized\");\n                break;\n            default:\n                throw new AssertionError();\n        }\n\n        return new QueryState(clientState);\n    }\n\n    void applyStateTransition(Message.Type requestType, Message.Type responseType)","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/transport/ServerConnection.java#L67-L103","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the client always sends a STARTUP frame (with CQL version options) before any other request","Fix connection-reuse logic so pooled connections are re-handshaken after reconnect","Check proxies/load balancers for frame replay or misrouted streams","If scanning/fuzzing traffic, use the correct protocol handshake or the CQL test harness"],"exampleFix":"// before (client sends query immediately)\nconnection.send(new QueryMessage(\"SELECT ...\", ...));\n// after\nconnection.send(new StartupMessage(options));\nconnection.send(new QueryMessage(\"SELECT ...\", ...));","handlingStrategy":"type-guard","validationCode":"if (connection.isFresh() && messageType != STARTUP && messageType != OPTIONS)\n    throw new IllegalStateException(\"first message on a new connection must be STARTUP or OPTIONS\");","typeGuard":"boolean validFirstMessage(Message.Type t) { return t == Message.Type.STARTUP || t == Message.Type.OPTIONS; }","tryCatchPattern":"try { connection.sendMessage(msg); } catch (DriverException e) { if (isProtocolExceptionContaining(e, \"expecting STARTUP or OPTIONS\")) reconnectAndHandshake(); else throw e; }","preventionTips":["Always run the full handshake on new/reused connections before sending requests","Never replay buffered frames onto a fresh connection","Use maintained drivers instead of hand-rolled protocol clients","Test reconnect logic against a real cluster"],"tags":["cql-protocol","state-machine","handshake","client-server"],"backgroundTag":"invalid-state-transition","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}