> ## 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.

# Kafka Schema Evolution and Compatibility

> The compatibility modes the Kafka Schema Registry enforces, what each one permits, and the order in which to upgrade producers and consumers.

Schemas change. Fields get added, renamed, and dropped, and each change risks breaking applications
that are already reading the topic. A compatibility mode turns that risk into a rule the registry
enforces at registration time, so an incompatible change fails when you deploy it rather than when a
consumer hits a record it can't decode.

## Compatibility modes

Each subject has one mode. When a producer registers a new schema, the registry compares it against
the versions already in that subject according to the mode.

| Mode                  | Guarantee                                                                         | Changes allowed                    | Compared against      |
| --------------------- | --------------------------------------------------------------------------------- | ---------------------------------- | --------------------- |
| `BACKWARD` (default)  | Consumers on the new schema can read data written under the previous version.     | Delete fields; add optional fields | Latest version        |
| `BACKWARD_TRANSITIVE` | Consumers on the new schema can read data written under **any** previous version. | Delete fields; add optional fields | All previous versions |
| `FORWARD`             | Consumers on the previous version can read data written under the new schema.     | Add fields; delete optional fields | Latest version        |
| `FORWARD_TRANSITIVE`  | Consumers on **any** previous version can read data written under the new schema. | Add fields; delete optional fields | All previous versions |
| `FULL`                | Both of the above, between the new schema and the previous version.               | Add or delete optional fields      | Latest version        |
| `FULL_TRANSITIVE`     | Both of the above, across every version.                                          | Add or delete optional fields      | All previous versions |
| `NONE`                | No guarantee. Every schema is accepted.                                           | All                                | N/A                   |
| `ALWAYS_INCOMPATIBLE` | No schema change is accepted once the subject has a version.                      | None                               | N/A                   |

**`BACKWARD` is the default** for every format.

<Note title="ALWAYS_INCOMPATIBLE is a StreamNative extension">
  `ALWAYS_INCOMPATIBLE` has no Confluent equivalent. Use it to freeze a subject—the first schema
  registers normally, and every subsequent registration is rejected. Confluent client libraries may not
  accept it as a value, in which case set it through the REST API.
</Note>

### The transitive property

The non-transitive modes check only against the **latest** version. The transitive modes check
against **every** version in the subject.

The difference matters over a series of changes that are each individually fine but collectively
aren't. Under `BACKWARD`, you can delete field `a` in v2 and re-add it with a different type in v3:
each step passes against its immediate predecessor, but a consumer on v3 can't read v1 data. Under
`BACKWARD_TRANSITIVE`, v3 is rejected.

Use a transitive mode when consumers may lag far behind, when you replay history, or when a topic
retains data long enough that old versions stay reachable.

## Order of upgrading clients

The mode determines which side you deploy first. Getting this backwards breaks consumers in
production even though every schema passed its check.

| Mode                              | Upgrade first | Why                                                                                                              |
| --------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------- |
| `BACKWARD`, `BACKWARD_TRANSITIVE` | **Consumers** | New-schema consumers can read old data, so upgrade every consumer before producers start writing the new schema. |
| `FORWARD`, `FORWARD_TRANSITIVE`   | **Producers** | Old-schema consumers can read new data, so producers can move first and consumers follow.                        |
| `FULL`, `FULL_TRANSITIVE`         | Either        | Both directions are guaranteed.                                                                                  |
| `NONE`, `ALWAYS_INCOMPATIBLE`     | N/A           | No guarantee, or no change permitted.                                                                            |

`BACKWARD` is the default because the consumer-first order matches how most teams deploy: roll out
readers, then flip writers.

## Per-format rules

The three formats do not share one definition of "compatible." A change that's safe in Avro can break
JSON Schema, and Protobuf has rules that come from its field-numbering model rather than from the
registry.

|                | Avro                               | JSON Schema                       | Protobuf                                        |
| -------------- | ---------------------------------- | --------------------------------- | ----------------------------------------------- |
| Add a field    | Compatible if it has a default     | Compatible if it isn't required   | Compatible with a new field number              |
| Delete a field | Compatible if it had a default     | Depends on `additionalProperties` | Reserve the field number                        |
| Rename a field | Use an alias                       | Breaking                          | Field number is identity; name changes are safe |
| Change a type  | Only within Avro's promotion rules | Narrowing is breaking             | Only within Protobuf's compatible-type rules    |

For Avro, compatibility follows the
[Avro schema resolution rules](https://avro.apache.org/docs/1.11.1/specification/#schema-resolution).
Defaults are what make a field addition backward compatible—a field with no default has nothing for
the reader to fall back on.

For JSON Schema, `additionalProperties` governs almost everything. A schema that allows additional
properties has an *open content model*, and adding a required property to one is a breaking change,
which produces the "open content model" error at registration.

For Protobuf, field numbers are identity. Never reuse a number you've retired—reserve it.

<Warning title="Protobuf and the forward modes">
  The compatibility table on [Overview](/kafka/governance/sr/overview#configurable-compatibility-modes)
  lists `FORWARD`, `FORWARD_TRANSITIVE`, `FULL`, and `FULL_TRANSITIVE` as unsupported for Protobuf. The
  API accepts those values on a Protobuf subject rather than rejecting them, but the combination isn't
  validated or supported. Use `BACKWARD` or `BACKWARD_TRANSITIVE` for Protobuf subjects.
</Warning>

Changing a subject's **format**—registering a JSON schema on a subject holding Avro versions—is
always rejected with HTTP 409, in every mode.

## Set the compatibility mode

Set the mode on a subject:

```shell theme={null}
curl -u "any-user:$API_KEY" \
  -X PUT -H "Content-Type: application/vnd.schemaregistry.v1+json" \
  --data '{"compatibility": "BACKWARD_TRANSITIVE"}' \
  https://<schema-registry-url>/config/orders-value
```

Read it back:

```shell theme={null}
curl -u "any-user:$API_KEY" https://<schema-registry-url>/config/orders-value
```

<Warning title="GET /config does not report the real default">
  The global `GET /config` always returns `{"compatibilityLevel":"NONE"}` regardless of what's actually
  in force. The real default is `BACKWARD`. Always read the per-subject `GET /config/{subject}` instead.

  There is also no `PUT /config`—the global default can't be changed through the API. Set the mode
  per subject.
</Warning>

## Test a schema before you register it

Check a candidate schema against a subject without registering it. This is the call to put in CI:

```shell theme={null}
curl -u "any-user:$API_KEY" \
  -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
  --data '{"schema": "{\"type\":\"record\",\"name\":\"Order\",\"fields\":[{\"name\":\"id\",\"type\":\"string\"}]}"}' \
  https://<schema-registry-url>/compatibility/subjects/orders-value/versions/latest
```

```json theme={null}
{"is_compatible": true}
```

Add `?verbose=true` to get the reason a check failed rather than just `false`.

To check against every version rather than one, post to
`/compatibility/subjects/{subject}/versions` instead. Both endpoints are **POST**.

## Next steps

<CardGroup cols={2}>
  <Card title="Key concepts" icon="lightbulb" href="/kafka/governance/sr/fundamentals/key-concepts">
    Subjects, versions, schema IDs, and the wire format.
  </Card>

  <Card title="Confluent compatibility" icon="list-check" href="/kafka/governance/sr/reference/confluent-compatibility">
    Behavior differences that affect compatibility handling.
  </Card>
</CardGroup>
