apache/iceberg · error · UnsupportedOperationException
Cannot convert type
Error message
Cannot convert type: ${data.getClass().getName()} What it means
RecordConverter.convert(Object, SchemaUpdate.Consumer) only accepts Kafka Connect Struct or Map values representing the top-level record. Any other Java type (e.g. primitives, lists, raw strings) cannot represent an Iceberg table row, so it throws UnsupportedOperationException naming the actual value class.
Solutions
- Ensure the Kafka record value is deserialized into a Connect Struct (set an appropriate value converter, e.g. JsonSchemaConverter with schema, or a schema-registry-backed converter).
- Wrap the raw value in a Map (map of field name -> value) so it goes down the convertStructValue path.
- Catch UnsupportedOperationException at the record level and route the record to a DLQ instead of failing the task.
Example fix
// before
Record r = converter.convert(record.value(), consumer); // value is a String
// after
Map<String, Object> wrapped = Collections.singletonMap("payload", record.value());
Record r = converter.convert(wrapped, consumer); Defensive patterns
Strategy: type-guard
Validate before calling
if (!(value instanceof Struct) && !(value instanceof Map)) {
throw new IllegalArgumentException("Top-level record must be Struct or Map, got " + value.getClass().getName());
} Type guard
boolean isConvertible(Object v) {
return v instanceof Struct || v instanceof Map;
} Try / catch
try {
Record r = converter.convert(value, consumer);
} catch (UnsupportedOperationException e) {
log.error("Unsupported record value type", e);
sendToDlq(record, e);
} Prevention
- Use a schema-registry-backed value converter so Connect records arrive as Structs.
- Verify the topic's Connect value schema type (struct) in connector config before running.
- Log record.value().getClass() in a test SMT to confirm payload shape before deploying.
When it happens
Trigger: Calling RecordConverter.convert with a value that is neither org.apache.kafka.connect.data.Struct nor java.util.Map, e.g. convert("some string", consumer), convert(List.of(1,2), consumer), or passing a numeric/byte[] payload.
Common situations: Kafka Connect record values that were not wrapped in a struct — plain JSON scalars/arrays from topics ingested via the Iceberg sink, or custom users of the connector API passing raw values instead of structured records.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Cannot convert date
- Cannot convert time
- Cannot convert timestamp
- Cannot convert timestamptz
- Cannot convert to binary
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/8f81f1b4393d2159.
Report an issue: GitHub.
Appendix: source
Thrown at kafka-connect/kafka-connect/src/main/java/org/apache/iceberg/connect/data/RecordConverter.java:118
private final NameMapping nameMapping;
private final IcebergSinkConfig config;
private final Map<Integer, Map<String, NestedField>> structNameMap = Maps.newHashMap();
RecordConverter(Table table, IcebergSinkConfig config) {
this.tableSchema = table.schema();
this.nameMapping = createNameMapping(table);
this.config = config;
}
Record convert(Object data) {
return convert(data, null);
}
Record convert(Object data, SchemaUpdate.Consumer schemaUpdateConsumer) {
if (data instanceof Struct || data instanceof Map) {
return convertStructValue(data, tableSchema.asStruct(), -1, schemaUpdateConsumer);
}
throw new UnsupportedOperationException("Cannot convert type: " + data.getClass().getName());
}
private NameMapping createNameMapping(Table table) {
String nameMappingString = table.properties().get(TableProperties.DEFAULT_NAME_MAPPING);
return nameMappingString != null ? NameMappingParser.fromJson(nameMappingString) : null;
}
private Object convertValue(
Object value, Type type, int fieldId, SchemaUpdate.Consumer schemaUpdateConsumer) {
if (value == null) {
return null;
}
switch (type.typeId()) {
case STRUCT:
return convertStructValue(value, type.asStructType(), fieldId, schemaUpdateConsumer);
case LIST:
return convertListValue(value, type.asListType(), schemaUpdateConsumer);
case MAP:View on GitHub (pinned to 86d9c8fc54)