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 loads a user-supplied DynamicRecordGeneratorSQL implementation via reflection and casts it to the expected interface; if the configured class does not implement DynamicRecordGeneratorSQL, the ClassCastException is rethrown as this IllegalArgumentException with the class name.

Source

Thrown at flink/v2.1/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. Verify the configured class implements org.apache.iceberg.flink.DynamicRecordGeneratorSQL with both (RowType) and (RowType, Map, Configuration) constructors
  2. Check the classpath for stale/multiple Iceberg versions (mvn dependency:tree) and align versions
  3. Correct the fully-qualified class name in the write property

Example fix

// before
write.dynamic-record-generator=com.example.MyGen (implements old interface)
// after
write.dynamic-record-generator=com.example.MyGen implements DynamicRecordGeneratorSQL {
  MyGen(RowType t); MyGen(RowType t, Map<String,String> props, Configuration c);
}
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try {
  generator = sink.createDynamicRecordGenerator(rowType);
} catch (IllegalArgumentException e) {
  LOG.error("Generator {} does not implement DynamicRecordGeneratorSQL; check classpath/versions", generatorImpl, e);
  throw e;
}

Prevention

When it happens

Trigger: Setting the dynamic record generator property (write.dynamic-record-generator or equivalent write property) to a class name that exists but implements a different interface or an older/newer interface version.

Common situations: Copy-pasting a generator class name from another Iceberg version; classpath pollution with duplicate/old Iceberg jars providing a different DynamicRecordGeneratorSQL; typo'd fully-qualified class name resolving to the wrong class.

Related errors


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