apache/iceberg · error · java.lang.IllegalArgumentException

Class %s does not implement DynamicRecordGeneratorSQL

Error message

Class %s does not implement DynamicRecordGeneratorSQL

What it means

IcebergTableSink.createDynamicRecordGenerator reflectively instantiates a user-configured DynamicTableRecordGenerator implementation via DynConstructors. When the loaded class exists but is not assignable to the expected interface, the ClassCastException is rethrown as this IllegalArgumentException, meaning the configured class implements the wrong interface.

Source

Thrown at flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/IcebergTableSink.java:295

            .withLocation(location)
            .withProperties(tableProperties)
            .create();
  }

  private DynamicTableRecordGenerator createDynamicRecordGenerator(String generatorImpl) {
    RowType rowType = (RowType) resolvedSchema.toSourceRowDataType().getLogicalType();

    DynConstructors.Ctor<DynamicTableRecordGenerator> ctor;

    try {
      ctor =
          DynConstructors.builder(DynamicTableRecordGenerator.class)
              .loader(IcebergTableSink.class.getClassLoader())
              .impl(generatorImpl, RowType.class)
              .buildChecked();
      return ctor.newInstance(rowType);
    } catch (ClassCastException e) {
      throw new IllegalArgumentException(
          String.format("Class %s does not implement DynamicRecordGeneratorSQL", generatorImpl), e);
    } catch (Exception e) {
      throw new RuntimeException(
          String.format("Failed to instantiate DynamicRecordGeneratorSQL %s", generatorImpl), e);
    }
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Make the configured class implement org.apache.iceberg.flink.DynamicTableRecordGenerator (the interface DynConstructors checks) and add the required RowType constructor.
  2. Verify the fully-qualified class name and that the jar containing it is on the Flink job classpath (lib/ or shaded into the job jar).
  3. Recompile the custom generator against the exact iceberg-flink version in use; interfaces can change between releases.
  4. Remove the generator option to fall back to Iceberg's default record generation if a custom generator is not actually needed.

Example fix

// before
public class MyGen { public MyGen(RowType t) {} } // wrong: no interface
// after
public class MyGen implements DynamicTableRecordGenerator {
  public MyGen(RowType rowType) { ... }
  @Override public DynamicRecord generate(...) { ... }
}
// config
'sink.dynamic-record-generator' = 'com.example.MyGen'
Defensive patterns

Strategy: validation

Validate before calling

// Before configuring the sink, verify the generator class implements the interface
Class<?> cls = Class.forName("com.example.MyGen", true,
    Thread.currentThread().getContextClassLoader());
if (!org.apache.iceberg.flink.DynamicTableRecordGenerator.class.isAssignableFrom(cls)) {
  throw new IllegalArgumentException(
      cls.getName() + " must implement DynamicTableRecordGenerator");
}
cls.getConstructor(org.apache.flink.table.types.logical.RowType.class); // must exist

Type guard

static boolean isDynamicRecordGenerator(String className, ClassLoader loader) {
  try {
    Class<?> c = Class.forName(className, false, loader);
    return org.apache.iceberg.flink.DynamicTableRecordGenerator.class.isAssignableFrom(c);
  } catch (ClassNotFoundException e) {
    return false;
  }
}

Try / catch

try {
  sink = ...; // sink creation that instantiates the generator
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().contains("does not implement")) {
    LOG.error("Generator implements the wrong interface; recompile against this iceberg-flink version");
  }
  throw e;
}

Prevention

When it happens

Trigger: Setting the sink's dynamic record generator option to a class that does not implement DynamicTableRecordGenerator (message text says DynamicRecordGeneratorSQL), e.g. a typo'd class name, a class implementing an older/renamed interface, or a class compiled against a different Iceberg/Flink version.

Common situations: Interface renamed between Iceberg releases so the configured class implements the old one; copying a class name from docs targeting another version; implementing a custom generator against the wrong interface; classloader shadowing loading a stale class from a fat jar.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/802fe25133e5ad20. Report an issue: GitHub.