{"record":{"id":"7024da651db959a2","repo":"apache/flink","slug":"tuple-type-expected-but-was","errorCode":null,"errorMessage":"Tuple type expected but was: {}","messagePattern":"Tuple type expected but was: (.+?)","errorType":"exception","errorClass":"InvalidTypesException","httpStatus":null,"severity":"error","filePath":"flink-core/src/main/java/org/apache/flink/api/common/typeinfo/Types.java","lineNumber":269,"sourceCode":"     *     public int getId() { return f0; }\n     *\n     *     public String getName() { return f1; }\n     *   }\n     * }\n     *\n     * Types.TUPLE(MyTuple.class)\n     * </pre>\n     *\n     * @param tupleSubclass A subclass of {@link org.apache.flink.api.java.tuple.Tuple0} till {@link\n     *     org.apache.flink.api.java.tuple.Tuple25} that defines all field types and does not add\n     *     any additional fields\n     */\n    public static <T extends Tuple> TypeInformation<T> TUPLE(Class<T> tupleSubclass) {\n        final TypeInformation<T> ti = TypeExtractor.createTypeInfo(tupleSubclass);\n        if (ti instanceof TupleTypeInfo) {\n            return ti;\n        }\n        throw new InvalidTypesException(\"Tuple type expected but was: \" + ti);\n    }\n\n    /**\n     * Returns type information for a POJO (Plain Old Java Object).\n     *\n     * <p>A POJO class is public and standalone (no non-static inner class). It has a public\n     * no-argument constructor. All non-static, non-transient fields in the class (and all\n     * superclasses) are either public (and non-final) or have a public getter and a setter method\n     * that follows the Java beans naming conventions for getters and setters.\n     *\n     * <p>A POJO is a fixed-length and null-aware composite type. Every field can be null\n     * independent of the field's type.\n     *\n     * <p>The generic types for all fields of the POJO can be defined in a hierarchy of subclasses.\n     *\n     * <p>Java Record classes can also be used as valid POJOs (even though they don't fulfill some\n     * of the above criteria). In this case Flink will use the record canonical constructor to\n     * create the objects.","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/apache/flink/blob/2f3c205e9266cb30240eb7f4fdab15cad629a70f/flink-core/src/main/java/org/apache/flink/api/common/typeinfo/Types.java#L251-L287","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nclass MyData { public int id; public String name; }\nTypes.TUPLE(MyData.class); // throws: not a Tuple subclass\n\n// after\nTypes.POJO(MyData.class);\n// or define a real tuple subclass:\nclass MyTuple extends Tuple2<Integer, String> {}\nTypes.TUPLE(MyTuple.class);","handlingStrategy":"type-guard","validationCode":"Class<?> c = tupleSubclass;\nif (!Tuple.class.isAssignableFrom(c)) {\n    throw new IllegalArgumentException(c + \" does not extend Tuple\");\n}\n// also verify no extra fields beyond the Tuple arity\nTypes.TUPLE((Class<? extends Tuple>) c);","typeGuard":"static boolean isTupleSubclass(Class<?> c) {\n    return Tuple.class.isAssignableFrom(c);\n}","tryCatchPattern":"try {\n    return Types.TUPLE(tupleSubclass);\n} catch (InvalidTypesException e) {\n    // fall back to POJO if it is not a real tuple\n    return Types.POJO(tupleSubclass);\n}","preventionTips":["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."],"tags":["type-information","tuple","pojo","type-extractor","validation"],"backgroundTag":null,"analyzedSha":"2f3c205e9266cb30240eb7f4fdab15cad629a70f","analyzedAt":"2026-08-14T08:48:24.518Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}