apache/beam · error · IllegalArgumentException
Cannot infer schema with a circular reference. Class
Error message
Cannot infer schema with a circular reference. Class: {} What it means
StaticSchemaInference.schemaFromClass tracks classes it is currently inferring in alreadyVisitedSchemas, inserting null as a sentinel. If the same TypeDescriptor is encountered again while its schema is still null, the class hierarchy is self-referential (a cycle), and schema inference cannot terminate, so this IllegalArgumentException is thrown.
Solutions
- Break the recursion: change the self-referential field to a non-inferred type (e.g. String id reference) or mark it to be ignored by the schema.
- Provide the schema for the recursive field manually (Schema.Field with an explicitly supplied FieldType.row(schema)) instead of inferring it.
- Use a custom FieldValueTypeSupplier or SchemaProvider that supplies the schema for the recursive class explicitly.
- Restructure the data model so cycles are represented via IDs/keys rather than object references.
Example fix
// before
class Node { Node next; } // circular reference
// after
class Node { String nextId; } // reference by id, no cycle Defensive patterns
Strategy: validation
Validate before calling
// detect self-referential fields before schema inference
for (java.lang.reflect.Field f : pojoClass.getDeclaredFields()) {
if (f.getType().isAssignableFrom(pojoClass))
throw new IllegalArgumentException("Circular reference: " + pojoClass + "." + f.getName());
} Type guard
boolean isRecursive(Class<?> c) {
for (java.lang.reflect.Field f : c.getDeclaredFields()) {
if (f.getType() == c) return true;
}
return false;
} Try / catch
try {
Schema s = Schema.of(pojoClass);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("circular reference")) {
throw new IllegalStateException("Break the cycle: reference by id or supply schema manually", e);
}
throw e;
} Prevention
- Never model recursive types as directly inferred schema classes
- Represent recursion with ids/foreign keys
- Supply explicit FieldType.row schemas for recursive fields
When it happens
Trigger: Calling Schema.of / StaticSchemaInference.schemaFromClass (directly or via fieldFromType) on a class that contains a field whose type is (or transitively reaches) the same class, e.g. class Node { Node next; } with @DefaultSchema inference.
Common situations: Recursive Java types (linked lists, trees, self-referencing POJOs) annotated for automatic schema inference; also mutual recursion A<->B.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Arrow schema conversion does not support Beam type
- Cannot call getFromRowFunction when there is no schema
- Cannot call getSchema when there is no schema
- Cannot call getToRowFunction when there is no schema
- Cannot convert between types that don't have equivalent…
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/897b629a3a4d513c.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/StaticSchemaInference.java:105
* Infer a schema from a Java class.
*
* <p>Takes in a function to extract a list of field types from a class. Different callers may
* have different strategies for extracting this list: e.g. introspecting public member variables,
* public getter methods, or special annotations on the class.
*/
public static Schema schemaFromClass(
TypeDescriptor<?> typeDescriptor, FieldValueTypeSupplier fieldValueTypeSupplier) {
return schemaFromClass(typeDescriptor, fieldValueTypeSupplier, new HashMap<>());
}
private static Schema schemaFromClass(
TypeDescriptor<?> typeDescriptor,
FieldValueTypeSupplier fieldValueTypeSupplier,
Map<TypeDescriptor<?>, Schema> alreadyVisitedSchemas) {
if (alreadyVisitedSchemas.containsKey(typeDescriptor)) {
Schema existingSchema = alreadyVisitedSchemas.get(typeDescriptor);
if (existingSchema == null) {
throw new IllegalArgumentException(
"Cannot infer schema with a circular reference. Class: "
+ typeDescriptor.getRawType().getTypeName());
}
return existingSchema;
}
alreadyVisitedSchemas.put(typeDescriptor, null);
Schema.Builder builder = Schema.builder();
for (FieldValueTypeInformation type : fieldValueTypeSupplier.get(typeDescriptor)) {
Schema.FieldType fieldType =
fieldFromType(type.getType(), fieldValueTypeSupplier, alreadyVisitedSchemas);
Schema.Field f =
type.isNullable()
? Schema.Field.nullable(type.getName(), fieldType)
: Schema.Field.of(type.getName(), fieldType);
if (type.getDescription() != null) {
f = f.withDescription(type.getDescription());
}
builder.addFields(f);View on GitHub (pinned to 12126d8942)