apache/kafka · error · IllegalStateException

Did not recognize recording level {configId}

Error message

Did not recognize recording level {configId}

What it means

Thrown by RecordingLevel.shouldRecord(int configId) when configId does not equal INFO.id, DEBUG.id, or TRACE.id. Unlike forId, this is an IllegalStateException because shouldRecord is given a configId that the enum switch cannot match, which the code treats as an impossible/internal-consistency condition rather than user input. It guards the metric-recording gating logic that decides whether a sensor emits at the configured verbosity.

Source

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

                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);
            }
        }
    }

    private final RecordingLevel recordingLevel;

    Sensor(Metrics registry, String name, Sensor[] parents, MetricConfig config, Time time,
           long inactiveSensorExpirationTimeSeconds, RecordingLevel recordingLevel) {
        super();
        this.registry = registry;
        this.name = Objects.requireNonNull(name);
        this.parents = parents == null ? new Sensor[0] : parents;
        this.metrics = new LinkedHashMap<>();
        this.stats = new ArrayList<>();
        this.config = config;
        this.time = time;
        this.inactiveSensorExpirationTimeMs = TimeUnit.MILLISECONDS.convert(inactiveSensorExpirationTimeSeconds, TimeUnit.SECONDS);
        this.lastRecordTime = time.milliseconds();

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Ensure MetricConfig.recordLevel() always returns a RecordingLevel obtained from RecordingLevel.forName / RecordingLevel.forId, never a hand-built id.
  2. If you build MetricConfig in tests, set the recordLevel from the enum constant (e.g. RecordingLevel.INFO) rather than poking an id field.
  3. When extending RecordingLevel with a new level, add the matching branch to shouldRecord so the new id is recognized.
  4. Add a guard before calling shouldRecord to validate the configured id is within [0, MAX_RECORDING_LEVEL_KEY].

Example fix

// before
MetricConfig cfg = new MetricConfig().recordLevel(/* raw int */ 5);

// after
MetricConfig cfg = new MetricConfig().recordLevel(RecordingLevel.DEBUG);
Defensive patterns

Strategy: validation

Validate before calling

int configId = /* from wire/config */;
if (configId != Sensor.RecordingLevel.INFO.id
        && configId != Sensor.RecordingLevel.DEBUG.id
        && configId != Sensor.RecordingLevel.TRACE.id) {
    // unknown recording level; do not call shouldRecord(configId)
    throw new IllegalArgumentException("Unknown recording level configId: " + configId);
}

Type guard

static boolean isKnownRecordingLevelConfigId(int configId) {
    return configId == Sensor.RecordingLevel.INFO.id
        || configId == Sensor.RecordingLevel.DEBUG.id
        || configId == Sensor.RecordingLevel.TRACE.id;
}

Prevention

When it happens

Trigger: A Sensor's shouldRecord() is invoked when the configured MetricConfig.recordLevel().id is not 0, 1, or 2. Because shouldRecord(configId) only has explicit branches for INFO/DEBUG/TRACE ids, any other numeric configId (negative, or > 2) lands in the else branch.

Common situations: A custom or mock MetricConfig returning a fabricated recordLevel id; memory corruption or uninitialized config in a test; a future RecordingLevel id added to the enum but the switch in shouldRecord not updated; deserialized config whose recordLevel was read with the wrong type width.

Related errors


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