apache/cassandra · error · org.apache.cassandra.transport.ProtocolException

Unknown opcode

Error message

Unknown opcode %d

What it means

Message.Type.fromOpcode maps the frame header's 1-byte opcode to a message type. It throws this ProtocolException when the opcode exceeds the opcode table size or has no registered type, meaning the frame is not a valid native-protocol message. A related error is thrown when the opcode's direction doesn't match (e.g. a REQUEST opcode arriving on a response path).

Solutions

  1. Fix client framing so each frame's header (version, flags, opcode, length) matches the actual body; resync the stream if bytes were lost.
  2. Verify client and server support the same protocol version (header version byte must match).
  3. Check for proxies/load balancers corrupting or re-segmenting the CQL stream.
  4. Capture the raw bytes of the offending frame to identify the bad opcode and source.

Example fix

// before: writing opcode as raw arbitrary value
header[4] = (byte) 0x7F; // bogus opcode
// after
header[4] = Opcode.QUERY; // use a defined opcode constant (0x07)
Defensive patterns

Strategy: try-catch

Validate before calling

// validate opcode before writing a frame header
if (opcode < 0 || opcode >= 0x0E || Opcode.fromOpcode(opcode) == null)
    throw new IllegalArgumentException("Invalid opcode: " + opcode);

Type guard

boolean isKnownOpcode(int op) { return op >= 0 && op < 0x0E; } // valid native protocol opcodes are 0x00-0x0D

Try / catch

try { connect(); } catch (ProtocolException e) {
    if (e.getMessage().startsWith("Unknown opcode")) {
        log.error("Frame desync or bogus opcode; reconnect and reset stream", e);
    }
}

Prevention

When it happens

Trigger: A frame header contains an opcode not in the protocol's opcode table (e.g. 0x30+), or a null entry in opcodeIdx — from corrupt streams, wrong protocol framing, or garbage bytes after a desync.

Common situations: TCP stream desync (a proxy or half-written client misframes messages); fuzzing; a client speaking an incompatible/imagined protocol version; bytes injected mid-stream.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/e3f33a2a23c58af9. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/transport/Message.java:140

            for (Type type : Type.values())
            {
                if (opcodeIdx[type.opcode] != null)
                    throw new IllegalStateException("Duplicate opcode");
                opcodeIdx[type.opcode] = type;
            }
        }

        Type(int opcode, Direction direction, Codec<?> codec)
        {
            this.opcode = opcode;
            this.direction = direction;
            this.codec = codec;
        }

        public static Type fromOpcode(int opcode, Direction direction)
        {
            if (opcode >= opcodeIdx.length)
                throw new ProtocolException(String.format("Unknown opcode %d", opcode));
            Type t = opcodeIdx[opcode];
            if (t == null)
                throw new ProtocolException(String.format("Unknown opcode %d", opcode));
            if (t.direction != direction)
                throw new ProtocolException(String.format("Wrong protocol direction (expected %s, got %s) for opcode %d (%s)",
                                                          t.direction,
                                                          direction,
                                                          opcode,
                                                          t));
            return t;
        }

        @VisibleForTesting
        public Codec<?> unsafeSetCodec(Codec<?> codec) throws NoSuchFieldException, IllegalAccessException
        {
            Codec<?> original = this.codec;
            Field field = Type.class.getDeclaredField("codec");
            field.setAccessible(true);

View on GitHub (pinned to 88fd0f6a0e)