apache/iceberg · error · java.lang.IllegalArgumentException

Class %s does not implement DynamicRecordGeneratorSQL

Error message

Class %s does not implement DynamicRecordGeneratorSQL

What it means

Thrown when a configured DynamicRecordGeneratorSQL implementation class was loaded but its instances cannot be cast to the expected DynamicRecordGeneratorSQL interface, so Flink aborts sink construction. The buildChecked lookup matched a constructor shape, but the type does not implement the required interface.

Source

Thrown at flink/v2.3/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. Make the configured class implement DynamicRecordGeneratorSQL (and its factory/ctor contract for RowType, Map, Configuration).
  2. Check for duplicate Iceberg jars in the Flink /lib and job jar; remove conflicting versions and rebuild shaded jar.
  3. Verify the fully-qualified class name in the property points to the intended implementation.

Example fix

// before
properties.setProperty("write.dynamic-record-generator", "com.example.MyGen"); // MyGen does not implement the interface
// after
public class MyGen implements DynamicRecordGeneratorSQL { ... }
Defensive patterns

Strategy: validation

Validate before calling

Class<?> clz = Class.forName(generatorImpl, true, IcebergTableSink.class.getClassLoader());
if (!DynamicRecordGeneratorSQL.class.isAssignableFrom(clz)) {
  throw new IllegalStateException(generatorImpl + " must implement DynamicRecordGeneratorSQL");
}

Type guard

if (obj instanceof DynamicRecordGeneratorSQL) { DynamicRecordGeneratorSQL gen = (DynamicRecordGeneratorSQL) obj; }

Prevention

When it happens

Trigger: Setting 'write-dynamic-record-generator' (or equivalent write property) to a class name that compiles a matching constructor but does not implement org.apache.iceberg.flink.DynamicRecordGeneratorSQL; classpath conflict where two versions of the interface exist and the impl implements the wrong one.

Common situations: Typo'd or hand-rolled generator class; stale jar on the classpath after upgrading Iceberg so the interface was repackaged/renamed; fat-jar shading putting DynamicRecordGeneratorSQL in two different classloaders.

Related errors


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