apache/kafka · error · IllegalArgumentException

key cannot be null.

Error message

key cannot be null.

What it means

Thrown as IllegalArgumentException by RecordHeaders.checkKey(String) when a null key is passed to lastHeader(key), headers(key), or remove(key). RecordHeaders stores headers keyed by String and uses equals comparisons on keys in iteration, so a null key would both NPE during comparison and is rejected as a programmer error at entry. The guard runs before any iteration, failing fast.

Source

Thrown at clients/src/main/java/org/apache/kafka/common/header/internals/RecordHeaders.java:126

    public Iterator<Header> iterator() {
        return closeAware(headers.iterator());
    }

    public void setReadOnly() {
        this.isReadOnly = true;
    }

    public boolean isReadOnly() {
        return isReadOnly;
    }

    public Header[] toArray() {
        return headers.isEmpty() ? Record.EMPTY_HEADERS : headers.toArray(new Header[0]);     
    }

    private void checkKey(String key) {
        if (key == null)
            throw new IllegalArgumentException("key cannot be null.");
    }

    private void canWrite() {
        if (isReadOnly)
            throw new IllegalStateException("RecordHeaders has been closed.");
    }

    private Iterator<Header> closeAware(final Iterator<Header> original) {
        return new Iterator<>() {
            @Override
            public boolean hasNext() {
                return original.hasNext();
            }

            public Header next() {
                return original.next();
            }

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Null-check the key before calling lastHeader/headers/remove, and decide explicitly whether to skip the header or throw a domain error.
  2. Trace the source of the key variable; if it comes from a POJO/mapping, fix the upstream mapping to never yield null for header keys.
  3. If iterating headers with a filter, prefer iterator() and filter by a non-null constant.
  4. Add a unit test that asserts the producer path never passes null keys (e.g. via a recording interceptor).

Example fix

// before
String traceId = span.context().traceId();   // may be null
headers.lastHeader(traceId);

// after
String traceId = span.context().traceId();
if (traceId != null) {
    headers.lastHeader(traceId);
}
Defensive patterns

Strategy: validation

Validate before calling

String key = ...;
if (key == null) {
    throw new IllegalArgumentException("header key must not be null");
}
headers.lastHeader(key); // or remove(key) / headers(key)

Type guard

static String requireHeaderKey(String key) {
    if (key == null) throw new IllegalArgumentException("header key must not be null");
    return key;
}

Try / catch

try {
    headers.remove(key);
} catch (IllegalArgumentException e) {
    // message: "key cannot be null."
    // caller passed a null header key; fix the upstream source of the key
}

Prevention

When it happens

Trigger: Calling record.headers().lastHeader(null), .headers(null), or .remove(null); passing a header key sourced from a variable that was never initialized or came from a null-producing mapping (e.g. a missing field in an upstream POJO mapped to header keys).

Common situations: Producer interceptor / serializer that converts POJO fields to headers but does not null-check field values; copy-paste of a header key constant that was renamed and now resolves to null; reactive pipeline where a null key slips through from a malformed input record.

Related errors


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