apache/iceberg · error · JsonToMapException
record value is not a string, use StringConverter
Error message
record value is not a string, use StringConverter
What it means
JsonToMapTransform requires the record value to be a raw JSON string. If record.value() is not a String, process() throws JsonToMapException('record value is not a string, use StringConverter'), telling you to convert the value to a String before this transform.
Solutions
- Insert org.apache.kafka.connect.transforms.Cast or a StringConverter step before JsonToMapTransform
- Set the value converter to a String-producing one (e.g. StringConverter or JsonConverter with schemas.enable=false plus cast to string)
- Ensure the source connector emits String values
- Use a different transform appropriate for Struct/Map values
Example fix
// before "transforms.json.type": "org.apache.iceberg.connect.transforms.JsonToMapTransform$Value" // after (add a cast first) "transforms.cast.type": "org.apache.kafka.connect.transforms.Cast$Value", "transforms.cast.spec": "string", "transforms.json.type": "org.apache.iceberg.connect.transforms.JsonToMapTransform$Value"
Defensive patterns
Strategy: type-guard
Validate before calling
boolean jsonTransformCompatible(SinkRecord r) {
return r.value() == null || r.value() instanceof String;
} Type guard
boolean isStringValue(SinkRecord r) {
return r.value() instanceof String;
} Try / catch
try {
return transform.apply(record);
} catch (JsonToMapException e) {
LOG.error("Value is not a String; prepend Cast$Value spec=string", e);
throw e;
} Prevention
- Insert Cast$Value with spec=string before JsonToMapTransform
- Don't chain this SMT after converters that produce Struct/Map values
- Verify value() type with kafka-console-consumer or a test pipeline
When it happens
Trigger: Inserting JsonToMapTransform after a converter produced a Map, byte[], Struct, or other non-String value — e.g. JsonConverter schemas.enable=true producing a Struct, or AvroConverter producing a Struct.
Common situations: Chaining SMTs where an upstream converter/transform already decoded the JSON; using ByteArrayConverter (value is byte[]); records produced by schema-based source connectors.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Expected json object for record.value after parsing
- record.value is not valid json for record.value
- Unexpected type for field
- Cannot convert date
- Cannot convert time
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/ed8a4e5036a52ab0.
Report an issue: GitHub.
Appendix: source
Thrown at kafka-connect/kafka-connect-transforms/src/main/java/org/apache/iceberg/connect/transforms/JsonToMapTransform.java:73
private static final Schema JSON_MAP_SCHEMA =
SchemaBuilder.struct()
.field(
ALL_JSON_SCHEMA_FIELD,
SchemaBuilder.map(Schema.STRING_SCHEMA, Schema.STRING_SCHEMA).optional().build())
.build();
@Override
public R apply(R record) {
if (record.value() == null) {
return record;
} else {
return process(record);
}
}
private R process(R record) {
if (!(record.value() instanceof String)) {
throw new JsonToMapException("record value is not a string, use StringConverter");
}
String json = (String) record.value();
JsonNode obj;
try {
obj = MAPPER.readTree(json);
} catch (Exception e) {
throw new JsonToMapException(
String.format(
"record.value is not valid json for record.value: %s", collectRecordDetails(record)),
e);
}
if (!(obj instanceof ObjectNode)) {
throw new JsonToMapException(
String.format(
"Expected json object for record.value after parsing: %s",View on GitHub (pinned to 86d9c8fc54)