Search before asking
What happened
Avro serialization fails when a SeaTunnelRowType contains a mixed-case field name.
SeaTunnelRowTypeToAvroSchemaConverter preserves the original field name when it creates the Avro schema, but RowToAvroConverter lowercases the field name before passing it to GenericRecordBuilder.set(...).
For example, a row type containing CustomerID produces an Avro schema field named CustomerID, while serialization attempts to set customerid. Avro field lookup is case-sensitive, so the builder cannot find the field and serialization fails before a record is written.
The same mismatch occurs for fields inside a nested ROW.
Expected behavior:
- The writer uses the same field names as the generated Avro schema.
- Mixed-case fields serialize at both the top level and inside nested rows.
- Existing lowercase fields continue to work unchanged.
Actual behavior:
- Top-level and nested mixed-case fields fail in
GenericRecordBuilder.set(...).
- The failure is in the shared Avro serialization path used by connectors including Kafka and Pulsar.
SeaTunnel Version
Current dev branch at commit 96048a7b81c92d743acc23b5bed652bdfef9d6e5.
SeaTunnel Config
Not configuration-dependent. The issue can be reproduced directly through the Avro conversion API with a SeaTunnelRowType containing mixed-case field names.
Running Command
./mvnw -pl seatunnel-formats/seatunnel-format-avro \
-Dskip.spotless=true \
-Dcheckstyle.skip=true \
-Dlicense.skip=true \
-Dtest=AvroConverterTest test
Error Exception
With focused top-level and nested regression cases added to AvroConverterTest, both new cases fail:
Tests run: 3, Failures: 0, Errors: 2, Skipped: 0
java.lang.NullPointerException:
Cannot invoke "org.apache.avro.Schema$Field.pos()" because "field" is null
The missing field is the lowercased lookup name, which is not present in the case-preserving schema.
Minimal reproduction
Top-level field:
SeaTunnelRowType rowType =
new SeaTunnelRowType(
new String[] {"CustomerID"},
new SeaTunnelDataType<?>[] {BasicType.INT_TYPE});
SeaTunnelRow row = new SeaTunnelRow(1);
row.setField(0, 42);
new RowToAvroConverter(rowType).convertRowToGenericRecord(row);
Nested field:
SeaTunnelRowType nestedType =
new SeaTunnelRowType(
new String[] {"InnerID"},
new SeaTunnelDataType<?>[] {BasicType.INT_TYPE});
SeaTunnelRowType rowType =
new SeaTunnelRowType(
new String[] {"payload"},
new SeaTunnelDataType<?>[] {nestedType});
SeaTunnelRow nestedRow = new SeaTunnelRow(1);
nestedRow.setField(0, 42);
SeaTunnelRow row = new SeaTunnelRow(1);
row.setField(0, nestedRow);
new RowToAvroConverter(rowType).convertRowToGenericRecord(row);
Root cause
The schema and writer therefore disagree whenever a field name contains an uppercase character.
Impact
The converter is shared by connector serialization paths. Kafka constructs AvroSerializationSchema in its default row serializer, and Pulsar does the same when Avro format is selected:
Any upstream schema that legitimately preserves names such as CustomerID, eventTime, or nested mixed-case fields can reach this failure.
Proposed fix and regression coverage
Use the exact field name in both GenericRecordBuilder.set(...) calls instead of lowercasing it. Add regression coverage for:
- A top-level mixed-case field.
- A mixed-case field inside a nested
ROW.
- Conversion back to
SeaTunnelRow to confirm a complete round trip.
As a validation of this direction, removing the two lowercase conversions makes both regressions pass. The complete seatunnel-format-avro module then passes with 6 tests, 0 failures, and 0 errors.
This is backward-compatible for existing lowercase field names. It does not change the generated schema, configuration, dependencies, serialized format, or public API; it only makes the writer use the schema's actual field names.
Zeta or Flink or Spark Version
Not engine-specific.
Java or Scala Version
Java 21 was used for reproduction.
Screenshots
Not applicable.
Are you willing to submit PR?
Code of Conduct
Search before asking
What happened
Avro serialization fails when a
SeaTunnelRowTypecontains a mixed-case field name.SeaTunnelRowTypeToAvroSchemaConverterpreserves the original field name when it creates the Avro schema, butRowToAvroConverterlowercases the field name before passing it toGenericRecordBuilder.set(...).For example, a row type containing
CustomerIDproduces an Avro schema field namedCustomerID, while serialization attempts to setcustomerid. Avro field lookup is case-sensitive, so the builder cannot find the field and serialization fails before a record is written.The same mismatch occurs for fields inside a nested
ROW.Expected behavior:
Actual behavior:
GenericRecordBuilder.set(...).SeaTunnel Version
Current
devbranch at commit96048a7b81c92d743acc23b5bed652bdfef9d6e5.SeaTunnel Config
Not configuration-dependent. The issue can be reproduced directly through the Avro conversion API with a
SeaTunnelRowTypecontaining mixed-case field names.Running Command
./mvnw -pl seatunnel-formats/seatunnel-format-avro \ -Dskip.spotless=true \ -Dcheckstyle.skip=true \ -Dlicense.skip=true \ -Dtest=AvroConverterTest testError Exception
With focused top-level and nested regression cases added to
AvroConverterTest, both new cases fail:The missing field is the lowercased lookup name, which is not present in the case-preserving schema.
Minimal reproduction
Top-level field:
Nested field:
Root cause
new Schema.Field(...): SeaTunnelRowTypeToAvroSchemaConverter.java.builder.set(fieldName.toLowerCase(), ...): RowToAvroConverter.java.ROWwriter also callsrecordBuilder.set(fieldNames[i].toLowerCase(), ...): RowToAvroConverter.java.The schema and writer therefore disagree whenever a field name contains an uppercase character.
Impact
The converter is shared by connector serialization paths. Kafka constructs
AvroSerializationSchemain its default row serializer, and Pulsar does the same when Avro format is selected:Any upstream schema that legitimately preserves names such as
CustomerID,eventTime, or nested mixed-case fields can reach this failure.Proposed fix and regression coverage
Use the exact field name in both
GenericRecordBuilder.set(...)calls instead of lowercasing it. Add regression coverage for:ROW.SeaTunnelRowto confirm a complete round trip.As a validation of this direction, removing the two lowercase conversions makes both regressions pass. The complete
seatunnel-format-avromodule then passes with 6 tests, 0 failures, and 0 errors.This is backward-compatible for existing lowercase field names. It does not change the generated schema, configuration, dependencies, serialized format, or public API; it only makes the writer use the schema's actual field names.
Zeta or Flink or Spark Version
Not engine-specific.
Java or Scala Version
Java 21 was used for reproduction.
Screenshots
Not applicable.
Are you willing to submit PR?
Code of Conduct