apache/flink · error · InvalidTypesException
POJO type expected but was: {}
Error message
POJO type expected but was: {} What it means
Thrown by Types.POJO(Class) when Flink's TypeExtractor cannot extract a valid PojoTypeInfo from the given class. The analyzer fell back to a different TypeInformation (typically GenericTypeInfo/Kryo, or TupleTypeInfo, or a primitive type) because the class violated one or more POJO requirements: not public, a non-static inner class, missing a public no-arg constructor, or fields that are neither public-and-non-final nor properly getter/setter accessible.
Source
Thrown at flink-core/src/main/java/org/apache/flink/api/common/typeinfo/Types.java:301
* <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.
*
* <p>If Flink's type analyzer is unable to extract a valid POJO type information with type
* information for all fields, an {@link
* org.apache.flink.api.common.functions.InvalidTypesException} is thrown. Alternatively, you
* can use {@link Types#POJO(Class, Map)} to specify all fields manually.
*
* @param pojoClass POJO class to be analyzed by Flink
*/
public static <T> TypeInformation<T> POJO(Class<T> pojoClass) {
final TypeInformation<T> ti = TypeExtractor.createTypeInfo(pojoClass);
if (ti instanceof PojoTypeInfo) {
return ti;
}
throw new InvalidTypesException("POJO type expected but was: " + ti);
}
/**
* Returns type information for a POJO (Plain Old Java Object) and allows to specify all fields
* manually.
*
* <p>A type is considered a FLink POJO type, if it fulfills the conditions below.
*
* <ul>
* <li>It is a public class, and standalone (not a non-static inner class)
* <li>It has a public no-argument constructor.
* <li>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.
* <li>It is a fixed-length, null-aware composite type with non-deterministic field order.
* Every field can be null independent of the field's type.
* </ul>
*View on GitHub (pinned to 2f3c205e92)
Solutions
- Ensure the class is public, top-level or static-inner, and has a public no-argument constructor.
- Make every non-static non-transient field either public (and non-final) or provide public JavaBeans-compliant getter and setter for it.
- If a field's type cannot be auto-resolved, use Types.POJO(Class, Map) to specify all fields and their TypeInformation manually.
- If the class genuinely cannot be made a POJO, use Types.GENERIC(Class) to accept Kryo serialization, or switch to a Tuple/Row/POJO-registered alternative.
- Run TypeExtractor.createTypeInfo(MyClass.class) in a unit test and inspect the returned TypeInformation to see what Flink inferred instead.
Example fix
// before — private fields, no accessors
public class Event {
private String id;
private long ts;
}
TypeInformation<Event> ti = Types.POJO(Event.class); // throws
// after — public fields or getters/setters + no-arg constructor
public class Event {
public String id;
public long ts;
public Event() {}
}
TypeInformation<Event> ti = Types.POJO(Event.class); // ok
// alternative — specify fields manually
Map<String, TypeInformation<?>> fields = new HashMap<>();
fields.put("id", Types.STRING);
fields.put("ts", Types.LONG);
TypeInformation<Event> ti = Types.POJO(Event.class, fields); Defensive patterns
Strategy: type-guard
Validate before calling
// Validate POJO requirements before calling Types.POJO
public static boolean isLikelyValidPojo(Class<?> clazz) {
int modifiers = clazz.getModifiers();
if (!Modifier.isPublic(modifiers)) return false;
if (clazz.isMemberClass() && !Modifier.isStatic(modifiers)) return false;
try {
clazz.getConstructor(); // public no-arg
} catch (NoSuchMethodException e) {
return false;
}
return true;
}
if (isLikelyValidPojo(MyType.class)) {
TypeInformation<MyType> ti = Types.POJO(MyType.class);
} Type guard
// Type guard that inspects extracted type info before assuming POJO
TypeInformation<MyType> extracted = TypeExtractor.createTypeInfo(MyType.class);
if (extracted instanceof PojoTypeInfo) {
PojoTypeInfo<MyType> pojo = (PojoTypeInfo<MyType>) extracted;
// safe to use as POJO
} else {
// fall back to Types.GENERIC or Types.POJO(Class, Map)
} Try / catch
try {
TypeInformation<MyType> ti = Types.POJO(MyType.class);
} catch (InvalidTypesException e) {
// log and fall back to manual POJO spec or GENERIC
Map<String, TypeInformation<?>> fields = Map.of(
"id", Types.STRING,
"ts", Types.LONG
);
ti = Types.POJO(MyType.class, fields);
} Prevention
- Write a unit test that calls Types.POJO on every POJO you register and asserts it returns PojoTypeInfo.
- Follow the POJO contract: public class, public no-arg constructor, public non-final fields or JavaBeans getters/setters.
- Use Lombok @NoArgsConstructor + @Getter/@Setter or @Data to satisfy accessor requirements.
- Avoid Object/abstract/unbounded-generic fields on POJOs; specify concrete types.
When it happens
Trigger: Calling Types.POJO(MyClass.class) where MyClass is a non-static inner class, has no public no-arg constructor, has private fields without JavaBeans getters/setters, has final fields, or has interface/generic fields the analyzer cannot resolve. Also triggered when the class is an enum, an interface, an array type, or is itself recognized as a Tuple subclass.
Common situations: Defining a DataStream or Table sink POJO with private fields and no getters/setters. Using a non-static inner class as a POJO. Annotating a class with Lombok @Builder without @NoArgsConstructor. Having a field whose type is Object or an unbounded generic. Migrating from Tuple to POJO and forgetting to add accessors.
Related errors
- Field '{}' could not be accessed.
- Invalid POJO field reference "{}".
- Unable to find field "{}" in type {}.
- Nested field expression "{}" not possible on atomic type {}.
- Wildcard expressions are not allowed here.
AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14).
Data as JSON: /api/errors/9f4813219514b82e.
Report an issue: GitHub.