{"record":{"id":"802fe25133e5ad20","repo":"apache/iceberg","slug":"class-s-does-not-implement-dynamicrecordgenerator","errorCode":null,"errorMessage":"Class %s does not implement DynamicRecordGeneratorSQL","messagePattern":"Class (.+?) does not implement DynamicRecordGeneratorSQL","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/IcebergTableSink.java","lineNumber":295,"sourceCode":"            .withLocation(location)\n            .withProperties(tableProperties)\n            .create();\n  }\n\n  private DynamicTableRecordGenerator createDynamicRecordGenerator(String generatorImpl) {\n    RowType rowType = (RowType) resolvedSchema.toSourceRowDataType().getLogicalType();\n\n    DynConstructors.Ctor<DynamicTableRecordGenerator> ctor;\n\n    try {\n      ctor =\n          DynConstructors.builder(DynamicTableRecordGenerator.class)\n              .loader(IcebergTableSink.class.getClassLoader())\n              .impl(generatorImpl, RowType.class)\n              .buildChecked();\n      return ctor.newInstance(rowType);\n    } catch (ClassCastException e) {\n      throw new IllegalArgumentException(\n          String.format(\"Class %s does not implement DynamicRecordGeneratorSQL\", generatorImpl), e);\n    } catch (Exception e) {\n      throw new RuntimeException(\n          String.format(\"Failed to instantiate DynamicRecordGeneratorSQL %s\", generatorImpl), e);\n    }\n  }\n}\n","sourceCodeStart":277,"sourceCodeEnd":303,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/IcebergTableSink.java#L277-L303","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npublic class MyGen { public MyGen(RowType t) {} } // wrong: no interface\n// after\npublic class MyGen implements DynamicTableRecordGenerator {\n  public MyGen(RowType rowType) { ... }\n  @Override public DynamicRecord generate(...) { ... }\n}\n// config\n'sink.dynamic-record-generator' = 'com.example.MyGen'","handlingStrategy":"validation","validationCode":"// Before configuring the sink, verify the generator class implements the interface\nClass<?> cls = Class.forName(\"com.example.MyGen\", true,\n    Thread.currentThread().getContextClassLoader());\nif (!org.apache.iceberg.flink.DynamicTableRecordGenerator.class.isAssignableFrom(cls)) {\n  throw new IllegalArgumentException(\n      cls.getName() + \" must implement DynamicTableRecordGenerator\");\n}\ncls.getConstructor(org.apache.flink.table.types.logical.RowType.class); // must exist","typeGuard":"static boolean isDynamicRecordGenerator(String className, ClassLoader loader) {\n  try {\n    Class<?> c = Class.forName(className, false, loader);\n    return org.apache.iceberg.flink.DynamicTableRecordGenerator.class.isAssignableFrom(c);\n  } catch (ClassNotFoundException e) {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  sink = ...; // sink creation that instantiates the generator\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"does not implement\")) {\n    LOG.error(\"Generator implements the wrong interface; recompile against this iceberg-flink version\");\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["flink","iceberg","reflection","configuration","class-hierarchy"],"backgroundTag":"class-not-found","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}