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

  1. 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.
  2. Provide the schema for the recursive field manually (Schema.Field with an explicitly supplied FieldType.row(schema)) instead of inferring it.
  3. Use a custom FieldValueTypeSupplier or SchemaProvider that supplies the schema for the recursive class explicitly.
  4. 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

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


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)