> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamnative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Use Avro Schemas

> Configure the Avro serializer and deserializer, choose between SpecificRecord and GenericRecord, and understand Avro's compatibility rules.

Avro is the most widely used format with the Schema Registry. It's compact on the wire, has a
well-specified resolution algorithm for reading data written under a different schema, and its
compatibility rules are the most predictable of the three formats.

## Configure the serializer

```java theme={null}
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
props.put(KafkaAvroSerializerConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
props.put(KafkaAvroSerializerConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
props.put(KafkaAvroSerializerConfig.USER_INFO_CONFIG, String.format("%s:%s", "any-user", apiKey));
```

And the deserializer:

```java theme={null}
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
props.put(KafkaAvroDeserializerConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
props.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, true);
```

Add the dependency:

```xml theme={null}
<dependency>
  <groupId>io.confluent</groupId>
  <artifactId>kafka-avro-serializer</artifactId>
  <version>7.5.0</version>
</dependency>
```

The Confluent artifacts come from Confluent's Maven repository:

```xml theme={null}
<repository>
  <id>confluent</id>
  <url>https://packages.confluent.io/maven/</url>
</repository>
```

## SpecificRecord or GenericRecord

Avro gives you two ways to represent a record in Java, and the choice affects how schema changes
reach your code.

### SpecificRecord

Generate a class from an `.avsc` file with the Avro Maven plugin, and work with typed getters and
setters:

```java theme={null}
Producer<String, Order> producer = new KafkaProducer<>(props);
Order order = Order.newBuilder()
    .setId("order-1")
    .setTotal(42.0)
    .build();
producer.send(new ProducerRecord<>("orders", order.getId().toString(), order));
```

```java theme={null}
props.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, true);

Consumer<String, Order> consumer = new KafkaConsumer<>(props);
for (ConsumerRecord<String, Order> record : consumer.poll(Duration.ofSeconds(1))) {
    Order order = record.value();
    System.out.println(order.getTotal());
}
```

Use it when the schema is stable and you want compile-time safety. The trade-off is that a schema
change means regenerating the class and redeploying—which is a feature, not a bug, if you want
schema drift to surface at build time.

### GenericRecord

Work with the schema at runtime, addressing fields by name:

```java theme={null}
Schema schema = new Schema.Parser().parse(schemaString);
GenericRecord order = new GenericData.Record(schema);
order.put("id", "order-1");
order.put("total", 42.0);

producer.send(new ProducerRecord<>("orders", "order-1", order));
```

```java theme={null}
// SPECIFIC_AVRO_READER_CONFIG defaults to false
for (ConsumerRecord<String, GenericRecord> record : consumer.poll(Duration.ofSeconds(1))) {
    Object total = record.value().get("total");
}
```

Use it when you don't know the schema at build time—routers, sinks, format converters, and any
application that handles whatever arrives. The cost is no compile-time checking: a renamed field
becomes a runtime null.

<Tip>
  Most applications should use `SpecificRecord`. Reach for `GenericRecord` when the code is genuinely
  schema-agnostic, not merely when generating classes feels like a chore.
</Tip>

## Compatibility rules

Avro compatibility follows the
[Avro schema resolution rules](https://avro.apache.org/docs/1.11.1/specification/#schema-resolution).
The practical version:

| Change                                                                           | Backward compatible | Forward compatible |
| -------------------------------------------------------------------------------- | ------------------- | ------------------ |
| Add a field **with** a default                                                   | Yes                 | Yes                |
| Add a field **without** a default                                                | No                  | Yes                |
| Delete a field **that had** a default                                            | Yes                 | Yes                |
| Delete a field **without** a default                                             | Yes                 | No                 |
| Rename a field                                                                   | Only with an alias  | Only with an alias |
| Widen a type within Avro's promotion rules (`int` → `long` → `float` → `double`) | Yes                 | No                 |
| Narrow a type                                                                    | No                  | Yes                |
| Add a value to an enum                                                           | No                  | Yes                |
| Add a branch to a union                                                          | No                  | Yes                |

**Defaults are what make evolution work.** A field with no default gives a reader nothing to fall
back on when the writer's data omits it, so almost every safe addition is an addition with a default.

To rename a field without breaking readers, keep the old name as an alias:

```json theme={null}
{"name": "orderTotal", "type": "double", "aliases": ["total"]}
```

The default compatibility mode is `BACKWARD`. See
[Evolution and compatibility](/kafka/governance/sr/fundamentals/schema-evolution).

## Logical types

Avro logical types—`timestamp-millis`, `decimal`, `uuid`, `date`—annotate a primitive with
semantic meaning:

```json theme={null}
{"name": "createdAt", "type": {"type": "long", "logicalType": "timestamp-millis"}}
```

Compatibility is evaluated on the underlying primitive, so changing the logical type while keeping
the primitive passes the check but may change how your application interprets the value. Treat a
logical-type change as a breaking change even when the registry accepts it.

## Schema references

Avro references a schema by its fully qualified record name. See
[Schema references](/kafka/governance/sr/fundamentals/schema-references).

## Normalization

`?normalize=true` performs real normalization for Avro, so schemas differing only in field ordering
or whitespace resolve to the same schema ID.

## Next steps

<CardGroup cols={2}>
  <Card title="Protobuf" icon="code" href="/kafka/governance/sr/fundamentals/formats/protobuf">
    Field numbers, imports, and null handling.
  </Card>

  <Card title="JSON Schema" icon="brackets-curly" href="/kafka/governance/sr/fundamentals/formats/json-schema">
    Open content models and per-construct compatibility.
  </Card>
</CardGroup>
