From 099f26a5cbc2d2c6596397c530c203915157a9ea Mon Sep 17 00:00:00 2001 From: mliem2k Date: Thu, 16 Jul 2026 00:12:33 +0800 Subject: [PATCH] fix(FieldBuilders): validate schema is not null in build() FieldBuilder implementations (avro 1.4 through 1.11) constructed a Schema.Field via new Schema.Field(name, schema, doc, default, order) without ever checking that schema had been set. Under older Avro versions this silently produced a Field backed by a null schema, which failed later in confusing ways. Avro 1.11.1+ tightened default value validation inside the Schema.Field constructor itself, so the same missing check now surfaces as an exception thrown from deep inside Avro's constructor instead of a clear error at the call site. Add an explicit null check for the schema at the top of build() in every FieldBuilder implementation, throwing an IllegalStateException with a clear message before Avro's own constructor is invoked. This matches the existing validation style used elsewhere in the codebase, for example AbstractSchemaBuilder#build() rejecting a missing type. Also add a regression test, testBuildFailsOnNullSchema, to FieldBuilderTest so the behavior is verified across all supported Avro versions via the helper-tests-allavro module. Closes #435 Signed-off-by: mliem2k --- .../compatibility/avro110/FieldBuilder110.java | 3 +++ .../compatibility/avro111/FieldBuilder111.java | 3 +++ .../avroutil1/compatibility/avro14/FieldBuilder14.java | 3 +++ .../avroutil1/compatibility/avro15/FieldBuilder15.java | 3 +++ .../avroutil1/compatibility/avro16/FieldBuilder16.java | 3 +++ .../avroutil1/compatibility/avro17/FieldBuilder17.java | 3 +++ .../avroutil1/compatibility/avro18/FieldBuilder18.java | 3 +++ .../avroutil1/compatibility/avro19/FieldBuilder19.java | 3 +++ .../avroutil1/compatibility/FieldBuilderTest.java | 10 ++++++++++ 9 files changed, 34 insertions(+) diff --git a/helper/impls/helper-impl-110/src/main/java/com/linkedin/avroutil1/compatibility/avro110/FieldBuilder110.java b/helper/impls/helper-impl-110/src/main/java/com/linkedin/avroutil1/compatibility/avro110/FieldBuilder110.java index fe4fc5c23..d26e43eba 100644 --- a/helper/impls/helper-impl-110/src/main/java/com/linkedin/avroutil1/compatibility/avro110/FieldBuilder110.java +++ b/helper/impls/helper-impl-110/src/main/java/com/linkedin/avroutil1/compatibility/avro110/FieldBuilder110.java @@ -146,6 +146,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Object avroFriendlyDefault; try { avroFriendlyDefault = avroFriendlyDefaultValue(_defaultVal); diff --git a/helper/impls/helper-impl-111/src/main/java/com/linkedin/avroutil1/compatibility/avro111/FieldBuilder111.java b/helper/impls/helper-impl-111/src/main/java/com/linkedin/avroutil1/compatibility/avro111/FieldBuilder111.java index 432f2d54d..a6076054f 100644 --- a/helper/impls/helper-impl-111/src/main/java/com/linkedin/avroutil1/compatibility/avro111/FieldBuilder111.java +++ b/helper/impls/helper-impl-111/src/main/java/com/linkedin/avroutil1/compatibility/avro111/FieldBuilder111.java @@ -103,6 +103,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Object avroFriendlyDefault; try { avroFriendlyDefault = avroFriendlyDefaultValue(_defaultVal); diff --git a/helper/impls/helper-impl-14/src/main/java/com/linkedin/avroutil1/compatibility/avro14/FieldBuilder14.java b/helper/impls/helper-impl-14/src/main/java/com/linkedin/avroutil1/compatibility/avro14/FieldBuilder14.java index 8c95511c7..d911f5ce8 100644 --- a/helper/impls/helper-impl-14/src/main/java/com/linkedin/avroutil1/compatibility/avro14/FieldBuilder14.java +++ b/helper/impls/helper-impl-14/src/main/java/com/linkedin/avroutil1/compatibility/avro14/FieldBuilder14.java @@ -121,6 +121,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Schema.Field result = new Schema.Field(_name, _schema, _doc, _defaultVal, _order); if (_props != null && !_props.isEmpty()) { Map clonedProps = getProps(result); diff --git a/helper/impls/helper-impl-15/src/main/java/com/linkedin/avroutil1/compatibility/avro15/FieldBuilder15.java b/helper/impls/helper-impl-15/src/main/java/com/linkedin/avroutil1/compatibility/avro15/FieldBuilder15.java index 51dfd99b0..a57d07e4c 100644 --- a/helper/impls/helper-impl-15/src/main/java/com/linkedin/avroutil1/compatibility/avro15/FieldBuilder15.java +++ b/helper/impls/helper-impl-15/src/main/java/com/linkedin/avroutil1/compatibility/avro15/FieldBuilder15.java @@ -125,6 +125,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Schema.Field result = new Schema.Field(_name, _schema, _doc, _defaultVal, _order); if (_props != null && !_props.isEmpty()) { Map clonedProps = getProps(result); diff --git a/helper/impls/helper-impl-16/src/main/java/com/linkedin/avroutil1/compatibility/avro16/FieldBuilder16.java b/helper/impls/helper-impl-16/src/main/java/com/linkedin/avroutil1/compatibility/avro16/FieldBuilder16.java index 3d5a6da0e..78fa5cfe9 100644 --- a/helper/impls/helper-impl-16/src/main/java/com/linkedin/avroutil1/compatibility/avro16/FieldBuilder16.java +++ b/helper/impls/helper-impl-16/src/main/java/com/linkedin/avroutil1/compatibility/avro16/FieldBuilder16.java @@ -108,6 +108,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Schema.Field result = new Schema.Field(_name, _schema, _doc, _defaultVal, _order); if (_props != null) { for (Map.Entry entry : _props.entrySet()) { diff --git a/helper/impls/helper-impl-17/src/main/java/com/linkedin/avroutil1/compatibility/avro17/FieldBuilder17.java b/helper/impls/helper-impl-17/src/main/java/com/linkedin/avroutil1/compatibility/avro17/FieldBuilder17.java index ef0c1b588..b4321355d 100644 --- a/helper/impls/helper-impl-17/src/main/java/com/linkedin/avroutil1/compatibility/avro17/FieldBuilder17.java +++ b/helper/impls/helper-impl-17/src/main/java/com/linkedin/avroutil1/compatibility/avro17/FieldBuilder17.java @@ -108,6 +108,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Schema.Field result = new Schema.Field(_name, _schema, _doc, _defaultVal, _order); if (_props != null) { Avro17Utils.setProps(result, _props); diff --git a/helper/impls/helper-impl-18/src/main/java/com/linkedin/avroutil1/compatibility/avro18/FieldBuilder18.java b/helper/impls/helper-impl-18/src/main/java/com/linkedin/avroutil1/compatibility/avro18/FieldBuilder18.java index 8df2fa332..3537d2aa4 100644 --- a/helper/impls/helper-impl-18/src/main/java/com/linkedin/avroutil1/compatibility/avro18/FieldBuilder18.java +++ b/helper/impls/helper-impl-18/src/main/java/com/linkedin/avroutil1/compatibility/avro18/FieldBuilder18.java @@ -111,6 +111,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } @SuppressWarnings("deprecation") //deprecated but faster Schema.Field result = new Schema.Field(_name, _schema, _doc, _defaultVal, _order); if (_props != null) { diff --git a/helper/impls/helper-impl-19/src/main/java/com/linkedin/avroutil1/compatibility/avro19/FieldBuilder19.java b/helper/impls/helper-impl-19/src/main/java/com/linkedin/avroutil1/compatibility/avro19/FieldBuilder19.java index 1bbfc68a7..9c0a32ba1 100644 --- a/helper/impls/helper-impl-19/src/main/java/com/linkedin/avroutil1/compatibility/avro19/FieldBuilder19.java +++ b/helper/impls/helper-impl-19/src/main/java/com/linkedin/avroutil1/compatibility/avro19/FieldBuilder19.java @@ -103,6 +103,9 @@ public FieldBuilder copyFromField() { @Override public Schema.Field build() { + if (_schema == null) { + throw new IllegalStateException("schema not set"); + } Object avroFriendlyDefault; try { avroFriendlyDefault = avroFriendlyDefaultValue(_defaultVal); diff --git a/helper/tests/helper-tests-allavro/src/test/java/com/linkedin/avroutil1/compatibility/FieldBuilderTest.java b/helper/tests/helper-tests-allavro/src/test/java/com/linkedin/avroutil1/compatibility/FieldBuilderTest.java index bdddc3306..b4631f356 100644 --- a/helper/tests/helper-tests-allavro/src/test/java/com/linkedin/avroutil1/compatibility/FieldBuilderTest.java +++ b/helper/tests/helper-tests-allavro/src/test/java/com/linkedin/avroutil1/compatibility/FieldBuilderTest.java @@ -110,6 +110,16 @@ public void testNewFieldSortOrder() throws Exception { } } + @Test + public void testBuildFailsOnNullSchema() throws Exception { + try { + AvroCompatibilityHelper.newField(null).setName("noSchema").build(); + Assert.fail("expected an exception"); + } catch (IllegalStateException expected) { + // pass - schema is required for a field to be built + } + } + private void setDefaultValues(Schema.Field field) { Assert.assertTrue(AvroCompatibilityHelper.fieldHasDefault(field)); Schema fieldSchema = field.schema();