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
- Make the configured class implement org.apache.iceberg.flink.DynamicTableRecordGenerator (the interface DynConstructors checks) and add the required RowType constructor.
- 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).
- Recompile the custom generator against the exact iceberg-flink version in use; interfaces can change between releases.
- 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
- Implement org.apache.iceberg.flink.DynamicTableRecordGenerator exactly (watch for interface renames across Iceberg versions).
- Compile the custom generator against the same iceberg-flink version as the runtime.
- Use fully-qualified class names in sink options; avoid typos and shadowed classes.
- Keep the generator class in the user job jar, not only on one node's lib directory.
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
- Failed to instantiate DynamicRecordGeneratorSQL %s
- Class %s does not implement DynamicRecordGeneratorSQL
- Fallback to use 'none' distribution mode, because there are
- Fallback to use 'none' distribution mode, because there are
- Cannot initialize AliyunClientFactory, missing no-arg constr
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/802fe25133e5ad20.
Report an issue: GitHub.