apache/flink · error · InvalidProgramException
Specifying keys via field positions is only valid for tuple
Error message
Specifying keys via field positions is only valid for tuple data types. Type: {type} What it means
Thrown by ExpressionKeys(int[], TypeInformation, boolean) when the type is not a tuple type (or not a CompositeType). Position-based key specification like keyBy(0) / groupBy(0,1) indexes tuple fields by ordinal; non-tuple types (POJOs, primitives, Rows) have no ordinal addressing, so the request is invalid.
Source
Thrown at flink-core/src/main/java/org/apache/flink/api/common/operators/Keys.java:240
public ExpressionKeys(TypeInformation<T> type) {
this(SELECT_ALL_CHAR, type);
}
/** Create int-based (non-nested) field position keys on a tuple type. */
public ExpressionKeys(int keyPosition, TypeInformation<T> type) {
this(new int[] {keyPosition}, type, false);
}
/** Create int-based (non-nested) field position keys on a tuple type. */
public ExpressionKeys(int[] keyPositions, TypeInformation<T> type) {
this(keyPositions, type, false);
}
/** Create int-based (non-nested) field position keys on a tuple type. */
public ExpressionKeys(int[] keyPositions, TypeInformation<T> type, boolean allowEmpty) {
if (!type.isTupleType() || !(type instanceof CompositeType)) {
throw new InvalidProgramException(
"Specifying keys via field positions is only valid "
+ "for tuple data types. Type: "
+ type);
}
if (type.getArity() == 0) {
throw new InvalidProgramException(
"Tuple size must be greater than 0. Size: " + type.getArity());
}
if (!allowEmpty && (keyPositions == null || keyPositions.length == 0)) {
throw new IllegalArgumentException("The grouping fields must not be empty.");
}
this.keyFields = new ArrayList<>();
if (keyPositions == null || keyPositions.length == 0) {
// use all tuple fields as key fields
keyPositions = createIncrIntArray(type.getArity());
} else {View on GitHub (pinned to 2f3c205e92)
Solutions
- For POJOs, use field-name expressions: groupBy("userId").
- For atomic types, use keyBy(value -> value) or the select-all expression.
- Wrap the data in a Tuple if positional access is desired.
Example fix
// before (Event is a POJO)
ds.groupBy(0);
// after
ds.groupBy("eventId"); Defensive patterns
Strategy: validation
Validate before calling
if (!type.isTupleType() || !(type instanceof CompositeType)) {
throw new IllegalArgumentException(
"Position-based keys require a tuple type; got " + type + ". Use a field-name expression for POJOs.");
} Type guard
static <T> boolean supportsPositionKeys(TypeInformation<T> t) {
return t.isTupleType() && t instanceof CompositeType;
} Prevention
- Use integer positions only for Tuple types; use field-name strings for POJOs.
- When switching a DataSet from Tuple to POJO, convert positional keys to name expressions.
- Add a helper that picks the key style based on the type's kind.
When it happens
Trigger: Calling groupBy(0) on a POJO DataSet; keyBy(0) on a stream of a primitive/String type; passing an int key position to a Row-typed or generic-typed dataset.
Common situations: Switching a DataSet from Tuple to POJO without changing positional keys to field-name expressions; keying a stream of a wrapper type by position; legacy code using integer positions on non-tuple types.
Related errors
- Tuple size must be greater than 0. Size: {type.getArity()}
- This type ({ffd.getType()}) cannot be used as key.
- The grouping fields must not be empty.
- Expression key may not be null.
- Unable to extract key from expression '{keyExpr}' on key {cT
AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14).
Data as JSON: /api/errors/db3bc11d8b43273b.
Report an issue: GitHub.