Apache Avro schema types
Apache Avro is a compact, schema-driven binary serialization format used heavily in Kafka topics and the Hadoop ecosystem. Unlike Protobuf, a schema is itself valid JSON, which makes it easy to store in a Schema Registry and evolve without recompiling code. This reference lists every type — primitives, complex types, and logical types — with the JSON shape needed in a .avsc file.
Three families of Avro types
1. Primitive types
Avro defines eight primitives, specified as a bare JSON string:
| Type | JSON | Notes |
|---|---|---|
null | "null" | The absence of a value |
boolean | "boolean" | true or false |
int | "int" | 32-bit signed integer, ZigZag-encoded |
long | "long" | 64-bit signed integer, ZigZag-encoded |
float | "float" | Single-precision IEEE 754 |
double | "double" | Double-precision IEEE 754 |
bytes | "bytes" | Sequence of 8-bit unsigned bytes |
string | "string" | Unicode character sequence (UTF-8) |
int and long use variable-length ZigZag encoding, so small absolute values (positive or negative) occupy very few bytes — one byte for values in −64 to 63.
2. Complex types
Complex types are JSON objects with a "type" field:
record— a named object with a list offields, each carrying a name and a nested schema.enum— a named type whose value is one of a fixed set ofsymbolsstrings.array— a list ofitemsall of the same schema.map— string keys mapping tovaluesall of the same schema.union— a value that is exactly one of several listed schemas, written as a JSON array:["null", "string"].fixed— exactlysizebytes, identified by name.
3. Logical types
Logical types annotate a primitive or complex base type with semantic meaning using a logicalType attribute. Readers that do not recognise the logical type silently fall back to the base type — forward compatibility is guaranteed.
| Logical type | Base type | Meaning |
|---|---|---|
decimal | bytes or fixed | Arbitrary-precision decimal, with precision and scale |
date | int | Days since Unix epoch (1970-01-01) |
time-millis | int | Milliseconds since midnight UTC |
time-micros | long | Microseconds since midnight UTC |
timestamp-millis | long | Milliseconds since Unix epoch |
timestamp-micros | long | Microseconds since Unix epoch |
duration | fixed(12) | Months, days, milliseconds (unsigned) |
uuid | string | RFC 4122 UUID string |
Schema evolution rules
Avro’s power comes from schema evolution: readers and writers can use different schema versions as long as the change is backward or forward compatible. Safe changes include:
- Adding a field with a default value.
- Removing a field that had a default value.
- Changing
inttolong(widening is safe).
Unsafe changes include renaming a field without an alias, removing a field without a default, and narrowing types.
Practical example
A nullable timestamp field inside a record:
{
"type": "record",
"name": "Event",
"namespace": "com.example",
"fields": [
{ "name": "id", "type": "string" },
{
"name": "createdAt",
"type": ["null", { "type": "long", "logicalType": "timestamp-millis" }],
"default": null
}
]
}
Key conventions: put "null" first in a union when the default is null — Avro requires the default value to match the first type in the union. Always set "default": null so existing data without the field can be read by a schema that adds it later. Use the filter below to find the exact JSON shape for any Avro type.