apache/seatunnel · error · IllegalArgumentException

Cannot convert to struct:

Error message

Cannot convert to struct: 

What it means

RowConverter.convertStructValue dispatches on the SeaTunnel SqlType of the value being converted into an Iceberg struct; only ROW (SeaTunnelRow) is convertible, anything else reaches default and throws IllegalArgumentException naming the SqlType.

Source

Thrown at seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/data/RowConverter.java:118

        return convertStructValue(row, rowType, tableSchema.asStruct(), -1, wrapper);
    }

    protected GenericRecord convertStructValue(
            Object value,
            SeaTunnelDataType<?> fromType,
            Types.StructType schema,
            int parentFieldId,
            SchemaChangeWrapper wrapper) {
        switch (fromType.getSqlType()) {
            case ROW:
                return convertToStruct(
                        (SeaTunnelRow) value,
                        (SeaTunnelRowType) fromType,
                        schema,
                        parentFieldId,
                        wrapper);
            default:
                throw new IllegalArgumentException(
                        "Cannot convert to struct: " + fromType.getSqlType().name());
        }
    }

    /** Convert RowType */
    private GenericRecord convertToStruct(
            SeaTunnelRow row,
            SeaTunnelRowType fromType,
            Types.StructType schema,
            int structFieldId,
            SchemaChangeWrapper wrapper) {
        GenericRecord result = GenericRecord.create(schema);
        String[] fieldNames = fromType.getFieldNames();
        for (int i = 0; i < fieldNames.length; i++) {
            String recordField = fieldNames[i];
            Type afterType = SchemaUtils.toIcebergType(fromType.getFieldType(i));
            Types.NestedField tableField = lookupStructField(recordField, schema, structFieldId);
            // add column

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Align the SeaTunnel source/transform schema so struct fields carry SeaTunnelRowType (ROW).
  2. Wrap non-row values into a SeaTunnelRow matching the struct schema before writing.
  3. Change the Iceberg table schema to a non-struct type matching the data.
  4. Catch IllegalArgumentException and log/reject the offending record.

Example fix

// before
row.setField(i, someString); // struct field
// after
row.setField(i, new SeaTunnelRow(new Object[]{...})); // SeaTunnelRowType for struct field
Defensive patterns

Strategy: type-guard

Validate before calling

if (fromType.getSqlType() != SqlType.ROW) { throw new IllegalArgumentException("Expected ROW for struct field, got " + fromType.getSqlType()); }

Type guard

boolean isRow(Object v, SeaTunnelType<?> t) { return t instanceof SeaTunnelRowType && v instanceof SeaTunnelRow; }

Try / catch

try { record = converter.convert(row, fromType, schema); } catch (IllegalArgumentException e) { log.error("Struct conversion failed: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Calling convert()/convertValue() with a fromType whose SqlType is not ROW at a position where the target Iceberg type is a struct (e.g. mapping a map/array/string into a struct field).

Common situations: SeaTunnel-to-Iceberg schema mismatches where a nested Iceberg struct field is fed by a non-row SeaTunnel column; wrong sink schema config or upstream transform producing a different shape.

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


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/c7d90029d561fbb3. Report an issue: GitHub.