apache/flink · error · InvalidTypesException
Tuple type expected but was: {}
Error message
Tuple type expected but was: {} What it means
Types.TUPLE(Class<T extends Tuple>) runs TypeExtractor.createTypeInfo(tupleSubclass) and requires the result to be a TupleTypeInfo. If the extracted TypeInformation is not a TupleTypeInfo (e.g., the class does not actually extend a Tuple0..Tuple25, or Flink classifies it as a POJO/other type), an InvalidTypesException is thrown reporting the actual extracted type. The class must extend a concrete TupleN and define all field types without adding extra fields.
Source
Thrown at flink-core/src/main/java/org/apache/flink/api/common/typeinfo/Types.java:269
* public int getId() { return f0; }
*
* public String getName() { return f1; }
* }
* }
*
* Types.TUPLE(MyTuple.class)
* </pre>
*
* @param tupleSubclass A subclass of {@link org.apache.flink.api.java.tuple.Tuple0} till {@link
* org.apache.flink.api.java.tuple.Tuple25} that defines all field types and does not add
* any additional fields
*/
public static <T extends Tuple> TypeInformation<T> TUPLE(Class<T> tupleSubclass) {
final TypeInformation<T> ti = TypeExtractor.createTypeInfo(tupleSubclass);
if (ti instanceof TupleTypeInfo) {
return ti;
}
throw new InvalidTypesException("Tuple type expected but was: " + ti);
}
/**
* Returns type information for a POJO (Plain Old Java Object).
*
* <p>A POJO class is public and standalone (no non-static inner class). It has a public
* no-argument constructor. All non-static, non-transient fields in the class (and all
* superclasses) are either public (and non-final) or have a public getter and a setter method
* that follows the Java beans naming conventions for getters and setters.
*
* <p>A POJO is a fixed-length and null-aware composite type. Every field can be null
* independent of the field's type.
*
* <p>The generic types for all fields of the POJO can be defined in a hierarchy of subclasses.
*
* <p>Java Record classes can also be used as valid POJOs (even though they don't fulfill some
* of the above criteria). In this case Flink will use the record canonical constructor to
* create the objects.View on GitHub (pinned to 2f3c205e92)
Solutions
- Ensure the class extends a concrete TupleN (Tuple0..Tuple25) and does not add fields beyond the tuple arity; then Types.TUPLE works.
- If the class is a POJO, use Types.POJO(MyClass.class) instead.
- If you just need a generic tuple, use Types.TUPLE(TypeInformation.of(String.class), ...) (the vararg overload) rather than a subclass.
Example fix
// before
class MyData { public int id; public String name; }
Types.TUPLE(MyData.class); // throws: not a Tuple subclass
// after
Types.POJO(MyData.class);
// or define a real tuple subclass:
class MyTuple extends Tuple2<Integer, String> {}
Types.TUPLE(MyTuple.class); Defensive patterns
Strategy: type-guard
Validate before calling
Class<?> c = tupleSubclass;
if (!Tuple.class.isAssignableFrom(c)) {
throw new IllegalArgumentException(c + " does not extend Tuple");
}
// also verify no extra fields beyond the Tuple arity
Types.TUPLE((Class<? extends Tuple>) c); Type guard
static boolean isTupleSubclass(Class<?> c) {
return Tuple.class.isAssignableFrom(c);
} Try / catch
try {
return Types.TUPLE(tupleSubclass);
} catch (InvalidTypesException e) {
// fall back to POJO if it is not a real tuple
return Types.POJO(tupleSubclass);
} Prevention
- Only pass classes that extend a concrete TupleN (Tuple0..Tuple25) to Types.TUPLE.
- Use Types.POJO for POJOs, or the Types.TUPLE(TypeInformation...) vararg for anonymous tuples.
- Add a unit test asserting Types.TUPLE returns a TupleTypeInfo for each subclass you register.
When it happens
Trigger: Calling Types.TUPLE(MyClass.class) where MyClass does not extend org.apache.flink.api.java.tuple.TupleN; a Tuple subclass that adds fields or hides generics so TypeExtractor returns a non-TupleTypeInfo (e.g., PojoTypeInfo); passing a Tuple subclass whose generic supertype is erased.
Common situations: Mistakenly passing a POJO or bean class to Types.TUPLE; a Tuple subclass that adds non-tuple fields causing POJO classification; refactoring a Tuple subclass into a plain POJO and forgetting to switch from Types.TUPLE to Types.POJO.
Related errors
- The given class is no array.
- The given class is no array.
- Return type {keyType} of KeySelector {keyExtractor.getClass(
- Tuple size must be greater than 0. Size: {type.getArity()}
- Tuple position is out of range: {f}
AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14).
Data as JSON: /api/errors/7024da651db959a2.
Report an issue: GitHub.