apache/flink · error · NullKeyFieldException

Unable to access field {} on object {}

Error message

Unable to access field {} on object {}

What it means

PojoComparator.accessField(field, object) wraps Field.get(object); a NullPointerException from reflection occurs when 'object' itself is null, and Flink surfaces it as NullKeyFieldException('Unable to access field ... on object null'). It means the POJO whose key field is being read is null.

Source

Thrown at flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/PojoComparator.java:194

    @SuppressWarnings({"rawtypes", "unchecked"})
    @Override
    public void getFlatComparator(List<TypeComparator> flatComparators) {
        for (int i = 0; i < comparators.length; i++) {
            if (comparators[i] instanceof CompositeTypeComparator) {
                ((CompositeTypeComparator) comparators[i]).getFlatComparator(flatComparators);
            } else {
                flatComparators.add(comparators[i]);
            }
        }
    }

    /** This method is handling the IllegalAccess exceptions of Field.get() */
    public final Object accessField(Field field, Object object) {
        try {
            object = field.get(object);
        } catch (NullPointerException npex) {
            throw new NullKeyFieldException(
                    "Unable to access field " + field + " on object " + object);
        } catch (IllegalAccessException iaex) {
            throw new RuntimeException(
                    "This should not happen since we call setAccesssible(true) in the ctor."
                            + " fields: "
                            + field
                            + " obj: "
                            + object);
        }
        return object;
    }

    @Override
    public int hash(T value) {
        int i = 0;
        int code = 0;
        for (; i < this.keyFields.length; i++) {
            code *= TupleComparatorBase.HASH_SALT[i & 0x1F];

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Filter out null records before keyBy/groupBy: stream.filter(Objects::nonNull)
  2. Fix the source/deserializer so it never emits null records (emit a Skip/retry or throw instead)
  3. Wrap records in a non-null container type (e.g. Tuple2 or a custom wrapper) if null must be representable

Example fix

// before
d stream
  .keyBy(pojo -> pojo.getUserId())
  ...

// after
d stream
  .filter(Objects::nonNull)
  .keyBy(pojo -> pojo.getUserId())
  ...
Defensive patterns

Strategy: validation

Validate before calling

// guard before keyBy/groupBy on POJOs
d stream
  .filter(Objects::nonNull)
  .keyBy(...);

Type guard

static <T> boolean isUsableAsPojoKey(T record) {
    return record != null;
}

Try / catch

try {
    comparator.hash(record);
} catch (NullKeyFieldException e) {
    // null record reached the comparator; upstream null leak
    throw new IllegalArgumentException("Null record reached keyed operator", e);
}

Prevention

When it happens

Trigger: groupBy/join/sort on a POJO key where a record in the stream/state is null: hash(), setReference(), compare(), etc. dereference keyFields[i] on a null POJO via accessField and reflection throws NPE.

Common situations: A source emits null elements (e.g. deserialization failure mapped to null, flatMap returning null); nulls inside keyed state or co-group inputs where POJO comparators are applied; inner nulls are fine but the top-level record being null is not.

Related errors


AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14). Data as JSON: /api/errors/1f7d7585bd971657. Report an issue: GitHub.