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
- 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
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
- 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
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
- Unexpected message , expecting
- Unexpected message STARTUP, the connection is already…
- Beta version of server used
- Can't finish migration, initiator=
- Can't proceed from the state
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)