{"record":{"id":"12ff54be34d5cee2","repo":"apache/iceberg","slug":"the-avro-schema-is-not-a-nullable-type-schema","errorCode":null,"errorMessage":"The Avro schema is not a nullable type: ${schema}","messagePattern":"The Avro schema is not a nullable type: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/formats/avro/RowDataToAvroConverters.java","lineNumber":292,"sourceCode":"      private static final long serialVersionUID = 1L;\n\n      @Override\n      public Object convert(Schema schema, Object object) {\n        if (object == null) {\n          return null;\n        }\n\n        // get actual schema if it is a nullable schema\n        Schema actualSchema;\n        if (schema.getType() == Schema.Type.UNION) {\n          List<Schema> types = schema.getTypes();\n          int size = types.size();\n          if (size == 2 && types.get(1).getType() == Schema.Type.NULL) {\n            actualSchema = types.get(0);\n          } else if (size == 2 && types.get(0).getType() == Schema.Type.NULL) {\n            actualSchema = types.get(1);\n          } else {\n            throw new IllegalArgumentException(\n                \"The Avro schema is not a nullable type: \" + schema.toString());\n          }\n        } else {\n          actualSchema = schema;\n        }\n        return converter.convert(actualSchema, object);\n      }\n    };\n  }\n\n  private static RowDataToAvroConverter createRowConverter(\n      RowType rowType, boolean legacyTimestampMapping) {\n    final RowDataToAvroConverter[] fieldConverters =\n        rowType.getChildren().stream()\n            .map(legacyType -> createConverter(legacyType, legacyTimestampMapping))\n            .toArray(RowDataToAvroConverter[]::new);\n    final LogicalType[] fieldTypes =\n        rowType.getFields().stream().map(RowType.RowField::getType).toArray(LogicalType[]::new);","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/formats/avro/RowDataToAvroConverters.java#L274-L310","documentation":"RowDataToAvroConverters wraps each converter to unwrap nullable Avro fields before converting RowData values to Avro. When the writer's Avro schema for a field is a UNION, it must be exactly a 2-branch union with one NULL branch (the standard Avro nullable pattern). Any other union shape (3+ branches, or two non-null branches) cannot be unwrapped, so an IllegalArgumentException naming the offending schema is thrown.","triggerScenarios":"Calling RowDataToAvroConverters.createConverter / converter.convert(schema, object) where schema.getType() == UNION but the union does not have exactly 2 branches with one being Schema.Type.NULL — e.g. union [int, long, null], [string, bytes], or [null] alone.","commonSituations":"Feeding a generic Avro schema produced outside Iceberg/Flink that uses multi-branch unions; schema evolution merging multiple types into one field; hand-written Avro schemas with unions like [\"null\",\"int\",\"long\"] passed to Flink's Avro writer.","solutions":["Inspect the field's Avro schema and reduce the union to exactly [T, null] or [null, T].","If multiple non-null types are needed, promote the field to a single wider type (e.g. use double instead of [int, double]).","Convert the value before writing so the union branch is resolved upstream, then pass the concrete (non-union) schema.","If the union is genuinely 2-branch nullable, verify branch order/content — e.g. a nested union like [[int, null], null] is still rejected."],"exampleFix":"// before\nSchema fieldSchema = schema.getField(\"f\").schema(); // [\"int\", \"long\", \"null\"]\nconverter.convert(fieldSchema, value); // throws\n// after\nSchema fieldSchema = Schema.createUnion(Schema.create(SchemaType.INT), Schema.create(SchemaType.NULL));\nconverter.convert(fieldSchema, value); // OK","handlingStrategy":"validation","validationCode":"static boolean isNullableUnion(Schema s) {\n  return s.getType() != Schema.Type.UNION\n      || (s.getTypes().size() == 2\n          && (s.getTypes().get(0).getType() == Schema.Type.NULL\n              || s.getTypes().get(1).getType() == Schema.Type.NULL));\n}\nif (!isNullableUnion(fieldSchema)) throw new IllegalStateException(\"Bad union: \" + fieldSchema);","typeGuard":"if (schema.getType() == Schema.Type.UNION\n    && schema.getTypes().size() == 2\n    && schema.getTypes().stream().anyMatch(t -> t.getType() == Schema.Type.NULL)) {\n  // safe to convert\n}","tryCatchPattern":"try {\n  converter.convert(fieldSchema, value);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().startsWith(\"The Avro schema is not a nullable type\")) {\n    // log schema, use resolved/concrete branch schema instead\n  } else throw e;\n}","preventionTips":["Only emit [T, null] or [null, T] unions in schemas feeding Flink Avro writers.","Validate all Avro schemas with a checker that rejects multi-branch unions at startup.","Avoid schema evolution that widens a field's union; widen the base type instead."],"tags":["avro","flink","serialization","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}