apache/kafka · error · IllegalArgumentException

Unexpected RecordLevel id `%d`, it should be between `%d` an

Error message

Unexpected RecordLevel id `%d`, it should be between `%d` and `%d` (inclusive)

What it means

Thrown by Sensor.RecordingLevel.forId(int) when deserializing a recording level from a numeric id that falls outside the valid range [0, MAX_RECORDING_LEVEL_KEY] (currently 0=INFO, 1=DEBUG, 2=TRACE). The library uses stable numeric ids to serialize RecordingLevel on the wire, so an out-of-range value means the sender and receiver disagree on the protocol enum. It is an IllegalArgumentException to surface the corrupt/unrecognized value as early as possible rather than indexing into ID_TO_TYPE with a bad subscript.

Source

Thrown at clients/src/main/java/org/apache/kafka/common/metrics/Sensor.java:114

            }
            ID_TO_TYPE = idToName;
            MAX_RECORDING_LEVEL_KEY = maxRL;
        }

        /** an english description of the api--this is for debugging and can change */
        public final String name;

        /** the permanent and immutable id of an API--this can't change ever */
        public final short id;

        RecordingLevel(int id, String name) {
            this.id = (short) id;
            this.name = name;
        }

        public static RecordingLevel forId(int id) {
            if (id < MIN_RECORDING_LEVEL_KEY || id > MAX_RECORDING_LEVEL_KEY)
                throw new IllegalArgumentException(String.format("Unexpected RecordLevel id `%d`, it should be between `%d` " +
                    "and `%d` (inclusive)", id, MIN_RECORDING_LEVEL_KEY, MAX_RECORDING_LEVEL_KEY));
            return ID_TO_TYPE[id];
        }

        /** Case insensitive lookup by protocol name */
        public static RecordingLevel forName(String name) {
            return RecordingLevel.valueOf(name.toUpperCase(Locale.ROOT));
        }

        public boolean shouldRecord(final int configId) {
            if (configId == INFO.id) {
                return this.id == INFO.id;
            } else if (configId == DEBUG.id) {
                return this.id == INFO.id || this.id == DEBUG.id;
            } else if (configId == TRACE.id) {
                return true;
            } else {
                throw new IllegalStateException("Did not recognize recording level " + configId);

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Validate or clamp the id against RecordingLevel.MAX_RECORDING_LEVEL_KEY before calling forId, and reject/log unknown ids at the deserialization boundary.
  2. Align client and broker to the same Kafka version so the RecordingLevel enum (and its id mapping) matches on both sides.
  3. If the id originates from user config, accept the string name (INFO/DEBUG/TRACE) and use RecordingLevel.forName(name) instead of a raw numeric id.
  4. When forking/customizing, add the missing enum constant in RecordingLevel so the id maps to a real level.

Example fix

// before
RecordingLevel level = RecordingLevel.forId(rawIdFromWire);

// after
if (rawIdFromWire < 0 || rawIdFromWire > RecordingLevel.MAX_RECORDING_LEVEL_KEY) {
    throw new IllegalStateException("Unknown recording level id from peer: " + rawIdFromWire);
}
RecordingLevel level = RecordingLevel.forId(rawIdFromWire);
Defensive patterns

Strategy: validation

Validate before calling

int id = /* from protocol/config */;
if (id < 0 || id > Sensor.RecordingLevel.MAX_RECORDING_LEVEL_KEY) {
    // reject or default instead of calling forId
    throw new IllegalArgumentException("Invalid recording level id: " + id);
}
Sensor.RecordingLevel level = Sensor.RecordingLevel.forId(id);

Type guard

static Sensor.RecordingLevel safeRecordingLevelForId(int id) {
    for (Sensor.RecordingLevel rl : Sensor.RecordingLevel.values()) {
        if (rl.id == id) return rl;
    }
    return null;
}

Prevention

When it happens

Trigger: Calling RecordingLevel.forId(id) with id < 0 or id > 2. Typically reached when a RecordingLevel is deserialized from a byte buffer/protocol struct (e.g. metrics-related request/response fields, or a config parsed from a numeric string) and the remote side sent a value not matching any INFO/DEBUG/TRACE id.

Common situations: Cross-version communication where a newer client/broker introduces a new RecordingLevel id that the older peer does not know; corrupted or manually-crafted protocol payloads; tests that construct a RecordingLevel from a hard-coded magic number that drifted from the enum. Also seen when a configuration property accepting a recording-level id is fed an arbitrary integer.

Related errors


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/9139b238e1c30af7.json. Report an issue: GitHub.