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
- Reuse the connection without resending STARTUP; create a brand-new connection if re-initialization is needed
- Fix reconnect/retry logic to establish a fresh socket instead of replaying the handshake
- Don't try to change compression/options mid-connection — set them in the initial STARTUP only
- 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
- Send STARTUP exactly once per connection lifetime
- On reconnect, create a new connection object rather than re-handshaking
- Set compression/options only in the initial STARTUP
- Review retry/recovery code for accidental handshake replay
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
- Unexpected message , expecting
- Unexpected message , expecting STARTUP or OPTIONS
- 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/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)