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
TupleComparator.hash(T) computes a hash over the configured key positions of a tuple. If a key position is >= the tuple's arity, getFieldNotNull throws IndexOutOfBoundsException, which this method converts into KeyFieldFieldOutOfBoundsException wrapping the offending position. The message text ('Field {fieldNumber} is accessed for a key, but out of bounds') comes from KeyFieldOutOfBoundsException. It means the comparator's keyPositions do not match the tuple type it is applied to.
Source
Thrown at flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/TupleComparator.java:63
// Comparator Methods
// --------------------------------------------------------------------------------------------
@SuppressWarnings("unchecked")
@Override
public int hash(T value) {
int i = 0;
try {
int code = this.comparators[0].hash(value.getFieldNotNull(keyPositions[0]));
for (i = 1; i < this.keyPositions.length; i++) {
code *= HASH_SALT[i & 0x1F]; // salt code with (i % HASH_SALT.length)-th salt
// component
code += this.comparators[i].hash(value.getFieldNotNull(keyPositions[i]));
}
return code;
} catch (NullFieldException nfex) {
throw new NullKeyFieldException(nfex);
} catch (IndexOutOfBoundsException iobex) {
throw new KeyFieldOutOfBoundsException(keyPositions[i]);
}
}
@SuppressWarnings("unchecked")
@Override
public void setReference(T toCompare) {
int i = 0;
try {
for (; i < this.keyPositions.length; i++) {
this.comparators[i].setReference(toCompare.getFieldNotNull(this.keyPositions[i]));
}
} catch (NullFieldException nfex) {
throw new NullKeyFieldException(nfex);
} catch (IndexOutOfBoundsException iobex) {
throw new KeyFieldOutOfBoundsException(keyPositions[i]);
}
}
View on GitHub (pinned to 2f3c205e92)
Solutions
- Verify every entry of the keyPositions array passed to TupleComparator satisfies 0 <= pos < tuple.getArity(); fix off-by-one key positions.
- After any tuple schema change, rebuild the comparator from the current TypeInformation (TypeInformation.createComparator(fields, sortOrder, ExecutionConfig)) instead of hand-maintaining indices.
- Add a one-time assertion in job setup: for each key pos, assert pos < tupleArity, failing fast on the client rather than at runtime.
Example fix
// before
// records are Tuple2<Long, String>, but key uses field 2 (0-based) or was written 1-based
TupleComparator<Tuple2<Long,String>> cmp = new TupleComparator<>(
new int[] {2}, new TypeComparator[] {new LongComparator()}, new TypeSerializer[] {LongSerializer.INSTANCE});
int h = cmp.hash(record); // throws KeyFieldOutOfBoundsException(2)
// after
TupleComparator<Tuple2<Long,String>> cmp = new TupleComparator<>(
new int[] {1}, ...); // valid 0-based index, or 0 for the first field
int h = cmp.hash(record); Defensive patterns
Strategy: validation
Validate before calling
public static void validateKeyPositions(int[] keyPositions, int tupleArity) {
for (int pos : keyPositions) {
if (pos < 0 || pos >= tupleArity) {
throw new IllegalArgumentException("key position " + pos
+ " out of bounds for tuple arity " + tupleArity);
}
}
} Try / catch
try {
int h = cmp.hash(record);
} catch (org.apache.flink.types.KeyFieldOutOfBoundsException e) {
// e.getMessage contains the offending 0-based field number; fix keyPositions
throw new IllegalStateException("Key spec/arity mismatch: " + e.getMessage(), e);
} Prevention
- Treat key positions as 0-based everywhere; document the convention at each call site.
- Build comparators via TypeInformation.createComparator instead of hand-coded arrays.
- Unit-test hash()/compare() with a representative record before wiring into a job.
When it happens
Trigger: Constructing a TupleComparator with keyPositions like {2} and applying it (via hash-based partitioning, groupBy, join/coGroup keys on the Java Tuple API) to a Tuple2 whose valid indices are 0..1. Also when a tuple field at a valid index is fine but the comparator config was built for a wider tuple type.
Common situations: Tuple arity changed (Tuple3 -> Tuple2) but the comparator/key spec was not updated. Key position constants off by one (1-based vs 0-based confusion: Flink data model is 0-based, old Pact API was 1-based). Reusing a comparator built for one TypeInformation against records of a different tuple class.
Related errors
- Field {fieldNumber} is accessed for a key, but out of bounds
- Tuple field expression "{}" out of bounds of {}.
- {}
- Field {fieldNumber} is null, but expected to hold a key.
- Cannot create Comparator for {typeClass.getCanonicalName()}.
AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14).
Data as JSON: /api/errors/53c8966250661a10.
Report an issue: GitHub.