{"record":{"id":"897b629a3a4d513c","repo":"apache/beam","slug":"cannot-infer-schema-with-a-circular-reference-class","errorCode":null,"errorMessage":"Cannot infer schema with a circular reference. Class: {}","messagePattern":"Cannot infer schema with a circular reference\\. Class: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/StaticSchemaInference.java","lineNumber":105,"sourceCode":"   * Infer a schema from a Java class.\n   *\n   * <p>Takes in a function to extract a list of field types from a class. Different callers may\n   * have different strategies for extracting this list: e.g. introspecting public member variables,\n   * public getter methods, or special annotations on the class.\n   */\n  public static Schema schemaFromClass(\n      TypeDescriptor<?> typeDescriptor, FieldValueTypeSupplier fieldValueTypeSupplier) {\n    return schemaFromClass(typeDescriptor, fieldValueTypeSupplier, new HashMap<>());\n  }\n\n  private static Schema schemaFromClass(\n      TypeDescriptor<?> typeDescriptor,\n      FieldValueTypeSupplier fieldValueTypeSupplier,\n      Map<TypeDescriptor<?>, Schema> alreadyVisitedSchemas) {\n    if (alreadyVisitedSchemas.containsKey(typeDescriptor)) {\n      Schema existingSchema = alreadyVisitedSchemas.get(typeDescriptor);\n      if (existingSchema == null) {\n        throw new IllegalArgumentException(\n            \"Cannot infer schema with a circular reference. Class: \"\n                + typeDescriptor.getRawType().getTypeName());\n      }\n      return existingSchema;\n    }\n    alreadyVisitedSchemas.put(typeDescriptor, null);\n    Schema.Builder builder = Schema.builder();\n    for (FieldValueTypeInformation type : fieldValueTypeSupplier.get(typeDescriptor)) {\n      Schema.FieldType fieldType =\n          fieldFromType(type.getType(), fieldValueTypeSupplier, alreadyVisitedSchemas);\n      Schema.Field f =\n          type.isNullable()\n              ? Schema.Field.nullable(type.getName(), fieldType)\n              : Schema.Field.of(type.getName(), fieldType);\n      if (type.getDescription() != null) {\n        f = f.withDescription(type.getDescription());\n      }\n      builder.addFields(f);","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/StaticSchemaInference.java#L87-L123","documentation":"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.","triggerScenarios":"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.","commonSituations":"Recursive Java types (linked lists, trees, self-referencing POJOs) annotated for automatic schema inference; also mutual recursion A<->B.","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."],"exampleFix":"// before\nclass Node { Node next; } // circular reference\n// after\nclass Node { String nextId; } // reference by id, no cycle","handlingStrategy":"validation","validationCode":"// detect self-referential fields before schema inference\nfor (java.lang.reflect.Field f : pojoClass.getDeclaredFields()) {\n  if (f.getType().isAssignableFrom(pojoClass))\n    throw new IllegalArgumentException(\"Circular reference: \" + pojoClass + \".\" + f.getName());\n}","typeGuard":"boolean isRecursive(Class<?> c) {\n  for (java.lang.reflect.Field f : c.getDeclaredFields()) {\n    if (f.getType() == c) return true;\n  }\n  return false;\n}","tryCatchPattern":"try {\n  Schema s = Schema.of(pojoClass);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"circular reference\")) {\n    throw new IllegalStateException(\"Break the cycle: reference by id or supply schema manually\", e);\n  }\n  throw e;\n}","preventionTips":["Never model recursive types as directly inferred schema classes","Represent recursion with ids/foreign keys","Supply explicit FieldType.row schemas for recursive fields"],"tags":["java","schema","recursion","inference"],"backgroundTag":"schema-validation-failed","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}