apache/flink · error · KeyFieldOutOfBoundsException

Field {fieldNumber} is accessed for a key, but out of bounds

Error message

Field {fieldNumber} is accessed for a key, but out of bounds in the record.

What it means

In TupleComparatorBase.compareToReference, an IndexOutOfBoundsException while comparing the reference records is rethrown as KeyFieldOutOfBoundsException(keyPositions[i]). Unlike the TupleComparator paths (which read fields off actual tuples), here the exception typically escapes from a field comparator whose stored reference record is a narrower tuple/object than the configured keyPositions expect.

Source

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

    @Override
    public int compareToReference(TypeComparator<T> referencedComparator) {
        TupleComparatorBase<T> other = (TupleComparatorBase<T>) referencedComparator;

        int i = 0;
        try {
            for (; i < this.keyPositions.length; i++) {
                @SuppressWarnings("unchecked")
                int cmp = this.comparators[i].compareToReference(other.comparators[i]);
                if (cmp != 0) {
                    return cmp;
                }
            }
            return 0;
        } catch (NullPointerException npex) {
            throw new NullKeyFieldException(keyPositions[i]);
        } catch (IndexOutOfBoundsException iobex) {
            throw new KeyFieldOutOfBoundsException(keyPositions[i]);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public int compareSerialized(DataInputView firstSource, DataInputView secondSource)
            throws IOException {
        if (deserializedFields1 == null) {
            instantiateDeserializationUtils();
        }

        int i = 0;
        try {
            for (; i < serializers.length; i++) {
                deserializedFields1[i] =
                        serializers[i].deserialize(deserializedFields1[i], firstSource);
                deserializedFields2[i] =
                        serializers[i].deserialize(deserializedFields2[i], secondSource);

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Re-validate keyPositions against every record type flowing through the operator; enforce a single tuple arity per operator.
  2. Build comparators from the stream's TypeInformation rather than hand-coding positions.
  3. Projection/union steps should guarantee a uniform tuple width before keyed operations.

Example fix

// before
// comparator built for Tuple3 key {2}; stream carries Tuple2
cmp.setReference(t2);
cmp.compareToReference(cmp2); // KeyFieldOutOfBoundsException(2)

// after
// project all inputs to Tuple3 first, or key on positions valid for Tuple2 (0,1)
int[] keyPositions = {1};
cmp.setReference(t2);
cmp.compareToReference(cmp2);
Defensive patterns

Strategy: validation

Validate before calling

validateKeyPositions(keyPositions, sampleRecord.getArity()); // all key pos < arity
cmp.setReference(sampleRecord);

Try / catch

try {
    cmp.compareToReference(other);
} catch (KeyFieldOutOfBoundsException e) {
    // reference record narrower than key spec: unify tuple arity upstream
}

Prevention

When it happens

Trigger: compareToReference after setReference stored records whose tuple arity is smaller than the comparator's key positions; the field-level comparator then indexes past the record during reference comparison and the IOOBE is wrapped with the tuple-level key position.

Common situations: Comparator reused across tuple types of different arity. Mixed-schema streams reaching one keyed operator. Hand-built keyPositions stale after a schema change (same family as 687-690).

Related errors


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