apache/iceberg · error · IllegalArgumentException

Class %s does not implement DynamicRecordGeneratorSQL

Error message

Class %s does not implement DynamicRecordGeneratorSQL

What it means

IcebergTableSink.createDynamicRecordGenerator reflects over the configured DynamicRecordGeneratorSQL implementation class and throws IllegalArgumentException when the instantiated object is not assignable to the DynamicRecordGeneratorSQL interface (surfaced as ClassCastException). It means the class configured as the generator does not implement the required interface.

Source

Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/IcebergTableSink.java:293

            .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)
              .impl(generatorImpl, RowType.class, Map.class, Configuration.class)
              .buildChecked();
      return ctor.newInstance(rowType, writeProps, fromReadableConfig());
    } 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);
    }
  }

  private Configuration fromReadableConfig() {
    return readableConfig instanceof Configuration
        ? (Configuration) readableConfig
        : Configuration.fromMap(readableConfig.toMap());
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Fix the configured class name to point at a class implementing DynamicRecordGeneratorSQL.
  2. Recompile/redeploy the custom generator against the same Iceberg Flink version.
  3. Verify with: DynamicRecordGeneratorSQL.class.isAssignableFrom(Class.forName(impl)) before configuring.

Example fix

// before
'table.write.dynamic-record-generator' = 'com.example.MyGen' // MyGen implements some other interface
// after
'table.write.dynamic-record-generator' = 'com.example.MyGen' // class MyGen implements DynamicRecordGeneratorSQL { ... }
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName(generatorImpl);
if (!DynamicRecordGeneratorSQL.class.isAssignableFrom(c)) {
  throw new IllegalStateException(generatorImpl + " does not implement DynamicRecordGeneratorSQL");
}

Type guard

boolean validGenerator(String name) {
  try { return DynamicRecordGeneratorSQL.class.isAssignableFrom(Class.forName(name)); }
  catch (ClassNotFoundException e) { return false; }
}

Try / catch

try { sink.write(...); } catch (IllegalArgumentException e) { /* check table property 'write.dynamic-record-generator' value */ }

Prevention

When it happens

Trigger: Setting the sink write property that names a dynamic record generator implementation class to a class that exists and instantiates but does not implement DynamicRecordGeneratorSQL (wrong interface, wrong artifact version).

Common situations: Typo/mistaken class name in table properties pointing at a similar class; the class implements an older/renamed interface after an upgrade; custom generator compiled against a different Iceberg version.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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