{"record":{"id":"e7e4f7983c7959a3","repo":"apache/pulsar","slug":"avro-schema-typed-union-is-not-supported","errorCode":null,"errorMessage":"Avro schema typed [UNION] is not supported","messagePattern":"Avro schema typed \\[UNION\\] is not supported","errorType":"validation","errorClass":"InvalidSchemaDataException","httpStatus":null,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java","lineNumber":95,"sourceCode":"                // This fallback is only enabled when schemaJsonAllowLegacyJacksonFormat=true (PIP-464).\n                try {\n                    JSON_SCHEMA_READER.readValue(data);\n                } catch (IOException ioe) {\n                    throwInvalidSchemaDataException(schemaData, ioe);\n                }\n            } else {\n                throwInvalidSchemaDataException(schemaData, e);\n            }\n        }\n    }\n\n    static void checkAvroSchemaTypeSupported(Schema schema) throws InvalidSchemaDataException {\n            switch (schema.getType()) {\n                case RECORD: {\n                    break;\n                }\n                case UNION: {\n                    throw new InvalidSchemaDataException(\n                            \"Avro schema typed [UNION] is not supported\");\n                }\n                default: {\n                    // INT, LONG, FLOAT, DOUBLE, BOOLEAN, STRING, BYTES.\n                    // ARRAY, MAP, FIXED, NULL.\n                    LOGGER.info().attr(\"type\", schema.getType()).log(\"Registering a special avro schema\");\n                }\n            }\n    }\n\n    private static void throwInvalidSchemaDataException(SchemaData schemaData,\n                                                        Throwable cause) throws InvalidSchemaDataException {\n        throw new InvalidSchemaDataException(\"Invalid schema definition data for \"\n            + schemaData.getType() + \" schema\", cause);\n    }\n\n    static class CompatibleNameValidator implements NameValidator {\n","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/validator/StructSchemaDataValidator.java#L77-L113","documentation":"StructSchemaDataValidator.checkAvroSchemaTypeSupported rejects an Avro schema whose root type is UNION. Pulsar's registry supports RECORD as the root of an Avro schema (plus scalar/array/map special types), but a top-level union cannot be stored as a topic schema. Nested unions inside record fields are fine; only the root union is rejected.","triggerScenarios":"Registering an AVRO (or a KEY_VALUE whose side is AVRO) schema parsed from a JSON definition like [\"null\", {\"type\":\"record\",...}] or [\"int\",\"string\"] — e.g. Schema.AVRO on a generic union, ReflectDatum-generated unions, or hand-written Avro JSON starting with \"[\".","commonSituations":"Avro codegen or frameworks producing optional-field root unions; devs using org.apache.avro.Schema unions to represent 'null or record'; copying an Avro field schema (union) instead of the record schema into the registry.","solutions":["Wrap the union in a RECORD: create a top-level record with a single field whose type is the union, and register that record.","Pick one concrete member of the union (typically the record type) and register it as the root schema.","If nullability is the concern, model optional fields as union-typed fields INSIDE the record — nested unions are supported.","Verify locally: new Schema.Parser().parse(json).getType() must not be Type.UNION before uploading."],"exampleFix":"// before\nString avro = \"[\\\"null\\\",{\\\"type\\\":\\\"record\\\",\\\"name\\\":\\\"User\\\",\\\"fields\\\":[...]}]\";\nSchemaInfo info = Schema.AVRO(Schema.Parser().parse(avro)).getSchemaInfo();\n// after\nString avro = \"{\\\"type\\\":\\\"record\\\",\\\"name\\\":\\\"UserRoot\\\",\\\"fields\\\":[\"\n    + \"{\\\"name\\\":\\\"user\\\",\\\"type\\\":[\\\"null\\\",\\\"User\\\"]}]}\";\nSchemaInfo info = Schema.AVRO(Schema.Parser().parse(avro)).getSchemaInfo();","handlingStrategy":"validation","validationCode":"org.apache.avro.Schema avro = new org.apache.avro.Schema.Parser().parse(avroJson);\nif (avro.getType() == org.apache.avro.Schema.Type.UNION) {\n    throw new IllegalArgumentException(\"Avro root schema must not be a UNION; wrap it in a RECORD\");\n}","typeGuard":"boolean isRegisterableAvroRoot(org.apache.avro.Schema schema) {\n    return schema.getType() == org.apache.avro.Schema.Type.RECORD\n        || schema.getType() != org.apache.avro.Schema.Type.UNION;\n}","tryCatchPattern":null,"preventionTips":["Parse the Avro JSON and assert getType() == RECORD before uploading to the registry.","Model nullability with unions on record fields, never as the root schema.","When reusing Avro field schemas, take the enclosing record schema, not the field's union schema."],"tags":["pulsar","avro","schema-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}