From a210b47ef7779eb2a356159af549b2218d0eb20b Mon Sep 17 00:00:00 2001 From: Michael Wikberg Date: Fri, 26 Jun 2026 12:23:22 +0300 Subject: [PATCH] fix(avro): order "null" first in union when default is null The avro-schema generator emitted an invalid union when a property combined a non-null type with an explicit `default: null` (commonly produced by `nullable: true` + `allOf` composition), e.g. `["model.Foo", "null"]` with `"default": null`. Per the Avro specification, a union's default value must match the FIRST branch of the union, so the default `null` is only valid when `"null"` is the first branch. The invalid ordering is accepted by most schema parsers but fails at read time when the default is applied during schema evolution, crashing consumers. The Swagger Parser represents an explicit `default: null` as a Jackson `NullNode` (a non-null Java object) rather than a Java `null`, so `toDefaultValue` returned the string "null" and the field was rendered through the concrete-default branch (`[, "null"]`). Treat an explicit null default as "no default" so the field falls through to the existing nullable-union form (`["null", ]` with `"default": null`), which is valid. Real (non-null) defaults are unaffected. Extends the issue6268 test spec with a nullable scalar and an allOf-composed model reference, both using `default: null`, and regenerates the sample. --- .../codegen/languages/AvroSchemaCodegen.java | 12 ++++++++++++ .../src/test/resources/3_0/issue6268.yaml | 14 ++++++++++++++ .../.openapi-generator/FILES | 1 + .../AvroDefaultNullReference.avsc | 15 +++++++++++++++ .../SampleModelToTestAvroDefaultValues.avsc | 12 ++++++++++++ 5 files changed, 54 insertions(+) create mode 100644 samples/openapi3/schema/petstore/avro-schema-issue6268/AvroDefaultNullReference.avsc diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java index 5ecf732ca5b5..2fe4d50812b4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java @@ -16,6 +16,7 @@ package org.openapitools.codegen.languages; +import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.models.media.Schema; import lombok.Getter; import lombok.Setter; @@ -170,6 +171,17 @@ public String toDefaultValue(Schema p) { return null; } + // The Swagger Parser represents an explicit `default: null` (common with + // `nullable: true`, e.g. via allOf composition) as a Jackson NullNode rather than a + // Java null. Treating it as a concrete default produces an invalid Avro union such as + // `["Foo", "null"]` with `"default": null`, because Avro requires a union's default + // value to match its FIRST branch. Treat an explicit null default as "no default" so + // the field falls through to the nullable-union form `["null", "Foo"]` with + // `"default": null`, which is valid. + if (p.getDefault() instanceof JsonNode && ((JsonNode) p.getDefault()).isNull()) { + return null; + } + if (ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p) || ModelUtils.isStringSchema(p)) { return "\"" + p.getDefault().toString() + "\""; } diff --git a/modules/openapi-generator/src/test/resources/3_0/issue6268.yaml b/modules/openapi-generator/src/test/resources/3_0/issue6268.yaml index ce909ec3084f..8951cc936ce5 100644 --- a/modules/openapi-generator/src/test/resources/3_0/issue6268.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/issue6268.yaml @@ -19,12 +19,26 @@ paths: description: successful operation components: schemas: + AvroDefaultNullReference: + type: object + properties: + id: + type: string SampleModelToTestAvroDefaultValues: type: object required: - tagsRequired - tagsRequiredWithDefault properties: + nullableScalarWithNullDefault: + type: string + nullable: true + default: null + nullableModelWithNullDefault: + nullable: true + default: null + allOf: + - $ref: '#/components/schemas/AvroDefaultNullReference' name: type: string default: 'defaultName' diff --git a/samples/openapi3/schema/petstore/avro-schema-issue6268/.openapi-generator/FILES b/samples/openapi3/schema/petstore/avro-schema-issue6268/.openapi-generator/FILES index fb590f3217a3..efe28ee32d26 100644 --- a/samples/openapi3/schema/petstore/avro-schema-issue6268/.openapi-generator/FILES +++ b/samples/openapi3/schema/petstore/avro-schema-issue6268/.openapi-generator/FILES @@ -1 +1,2 @@ +AvroDefaultNullReference.avsc SampleModelToTestAvroDefaultValues.avsc diff --git a/samples/openapi3/schema/petstore/avro-schema-issue6268/AvroDefaultNullReference.avsc b/samples/openapi3/schema/petstore/avro-schema-issue6268/AvroDefaultNullReference.avsc new file mode 100644 index 000000000000..fccbac6e1854 --- /dev/null +++ b/samples/openapi3/schema/petstore/avro-schema-issue6268/AvroDefaultNullReference.avsc @@ -0,0 +1,15 @@ +{ + "namespace": "model", + "type": "record", + "doc": "", + "name": "AvroDefaultNullReference", + "fields": [ + { + "name": "id", + "type": ["null", "string"], + "doc": "", + "default": null + } + ] + +} diff --git a/samples/openapi3/schema/petstore/avro-schema-issue6268/SampleModelToTestAvroDefaultValues.avsc b/samples/openapi3/schema/petstore/avro-schema-issue6268/SampleModelToTestAvroDefaultValues.avsc index 1ff0ad5289e0..6a5ecf7e4f23 100644 --- a/samples/openapi3/schema/petstore/avro-schema-issue6268/SampleModelToTestAvroDefaultValues.avsc +++ b/samples/openapi3/schema/petstore/avro-schema-issue6268/SampleModelToTestAvroDefaultValues.avsc @@ -4,6 +4,18 @@ "doc": "", "name": "SampleModelToTestAvroDefaultValues", "fields": [ + { + "name": "nullableScalarWithNullDefault", + "type": ["null", "string"], + "doc": "", + "default": null + }, + { + "name": "nullableModelWithNullDefault", + "type": ["null", "model.AvroDefaultNullReference"], + "doc": "", + "default": null + }, { "name": "name", "type": ["string", "null"],