- Overview
- Compatibility Modes
- Configuration Resolution
- Avro Compatibility Rules
- JSON Schema Compatibility Rules
- Protobuf Compatibility Rules
- Checking Compatibility via API
- Compatibility Groups
- Related Documentation
Compatibility checking ensures that new schema versions can coexist with previous versions. The registry checks compatibility at registration time -- when a new schema version is registered via POST /subjects/{subject}/versions. If the proposed schema is incompatible with existing versions (under the active compatibility mode), the registry rejects the registration with HTTP 409 and an error message describing the incompatibility.
Compatibility is not enforced retroactively. Changing the compatibility level for a subject does not re-validate previously registered schemas. It only affects future registrations.
| Mode | Backward | Forward | Transitive | Description |
|---|---|---|---|---|
| NONE | No | No | No | No compatibility checking |
| BACKWARD | Yes | No | No | New schema can read data written by the latest previous schema |
| BACKWARD_TRANSITIVE | Yes | No | Yes | New schema can read data written by ALL previous schemas |
| FORWARD | No | Yes | No | Latest previous schema can read data written by new schema |
| FORWARD_TRANSITIVE | No | Yes | Yes | ALL previous schemas can read data written by new schema |
| FULL | Yes | Yes | No | Both backward and forward compatible with latest |
| FULL_TRANSITIVE | Yes | Yes | Yes | Both backward and forward compatible with ALL previous |
The default mode is BACKWARD.
Backward compatibility answers the question: "Can a new consumer read old data?"
A new consumer using the new schema can deserialize messages that were written with the previous schema. In reader/writer terminology, the new schema is the reader and the old schema is the writer.
Forward compatibility answers the question: "Can an old consumer read new data?"
An existing consumer using the old schema can deserialize messages that were written with the new schema. The old schema is the reader and the new schema is the writer.
Full compatibility requires both backward and forward compatibility simultaneously.
Non-transitive modes (BACKWARD, FORWARD, FULL) check compatibility only against the latest previous version. This is sufficient when schemas evolve incrementally and every consumer upgrades through each version in order.
Transitive modes (BACKWARD_TRANSITIVE, FORWARD_TRANSITIVE, FULL_TRANSITIVE) check compatibility against all previous versions. This is necessary when consumers MAY skip versions -- for example, when a consumer running schema version 1 needs to read data written with schema version 5 without having processed versions 2 through 4.
The compatibility level for a subject is resolved in this order of precedence:
- Per-subject override -- set via
PUT /config/{subject} - Global override -- set via
PUT /config - Config file default -- the
compatibility.default_levelsetting in the YAML config
This means:
- The config file default is the baseline that applies when no runtime overrides exist.
- An administrator can override the default globally at runtime via the API. This override is persisted in the storage backend.
- An administrator can override the compatibility level for individual subjects. This takes highest priority.
DELETE /config/{subject}removes the per-subject override, causing the subject to fall back to the global setting.DELETE /configremoves the global runtime override, causing it to fall back to the config file default.- Changes only affect future registrations. Existing schemas are not re-validated.
Set the global compatibility level:
curl -X PUT http://localhost:8081/config \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"compatibility": "FULL"}'Set the compatibility level for a specific subject:
curl -X PUT http://localhost:8081/config/my-subject \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"compatibility": "NONE"}'Read the effective compatibility level for a subject (reflects the full resolution chain):
curl http://localhost:8081/config/my-subjectResponse:
{"compatibilityLevel": "NONE"}Remove the per-subject override:
curl -X DELETE http://localhost:8081/config/my-subjectRemove the global runtime override:
curl -X DELETE http://localhost:8081/configThe Avro compatibility checker follows the Avro specification rules for schema resolution. Compatibility is checked by attempting to read data written with one schema using the other schema.
- Add a field with a default value. The new reader schema has a field that the old writer schema does not. When reading old data, the default value is used.
- Remove a field. The old writer schema has a field that the new reader schema does not. The old field is ignored when reading.
- Remove a field. The old reader still has the field; the new writer no longer writes it. The old reader uses its default value (if present).
- Add a field without a default. The old reader ignores the new field.
- Adding a field without a default is backward-incompatible. The new reader cannot construct a value for this field when reading old data.
- Removing a field without a default is forward-incompatible. The old reader cannot construct a value for the removed field when reading new data.
- Changing a field type to an incompatible type (for example,
stringtoint). - Renaming a record without using aliases.
Avro supports the following numeric promotions (widening):
| Writer Type | Allowed Reader Types |
|---|---|
int |
long, float, double |
long |
float, double |
float |
double |
string |
bytes |
bytes |
string |
Avro aliases are supported for both record and field renaming. If a reader schema renames a record or field, the checker resolves the old name through the aliases defined on the reader or writer schema. This allows controlled renaming without breaking compatibility.
When one side of a compatibility check is a union and the other is not, the checker verifies that the non-union type is compatible with at least one type in the union. When both sides are unions, every writer union type must be compatible with at least one reader union type.
When comparing enums, every writer symbol must exist in the reader enum (or the reader must define a default enum value). The reader may have additional symbols.
The JSON Schema compatibility checker supports both Draft-07 and Draft 2020-12 schemas. Compatibility is determined by analyzing whether the new schema accepts all values that the old schema accepts (backward) or vice versa (forward).
- Add optional properties. New properties that are not in the
requiredarray. - Remove required properties. Properties removed from
required. - Widen types. For example, changing
integertonumber. - Remove constraints. Removing
enum,const,pattern,not, ordependencies. - Relax numeric bounds. Decreasing
minimum/minLength/minItems/minPropertiesor increasingmaximum/maxLength/maxItems/maxProperties.
- Add required properties. A new property in
requiredthat did not exist before. - Change property from optional to required.
- Change type. For example, changing
stringtointeger(exceptintegertonumber, which is a valid widening). - Add constraints. Adding
enum,const,pattern,not,uniqueItems,dependencies,dependentRequired, ordependentSchemaswhere none existed. - Remove enum values. Removing a value from an
enumarray. - Tighten numeric bounds. Increasing
minimum/minLength/minItems/minPropertiesor decreasingmaximum/maxLength/maxItems/maxProperties. - Close the content model. Changing
additionalPropertiesfromtrue(or absent) tofalse. - Add properties to an open content model. If the old schema allowed
additionalProperties(the default), adding a new typed property is incompatible because old data may already contain values of a different type for that property name.
The JSON Schema checker evaluates the following keywords:
type(withintegertonumberpromotion)properties,required,additionalProperties,patternPropertiesitems,prefixItems,additionalItems,minItems,maxItems,uniqueItemsenum,constminimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOfminLength,maxLength,patternminProperties,maxPropertiesoneOf,anyOf,allOfnotdependencies(Draft-07)dependentRequired,dependentSchemas(Draft 2020-12)$refresolution (local references via#/definitions/and#/$defs/)
The Protobuf compatibility checker works at the wire-format level. Protobuf's binary encoding uses field numbers and wire types rather than field names, so many changes that look significant at the source level are actually wire-compatible.
- Add new fields (with new field numbers). Old data simply lacks the new fields; readers use default values.
- Remove optional/repeated fields. New readers ignore unknown field numbers in old data.
- Add new enum values. Unknown enum values are preserved as their numeric value.
- Add new messages. No wire impact on existing messages.
- Change field names. Field names are not encoded on the wire; only numbers matter.
- Remove enum type definitions. Enum fields are integers on the wire.
- Change a field number. The reader cannot match the old data to the correct field.
- Change a field type to a wire-incompatible type. For example,
int32tostringuses different wire encoding. - Remove a required field (proto2
requiredkeyword). - Add a required field (proto2
requiredkeyword). - Remove a field from a oneof. Changes the semantics of the oneof group.
- Move a field into an existing oneof that already has other members. This adds a mutual exclusion constraint that did not exist before.
- Remove a message that is referenced by other messages.
- Change the package name.
Fields within the same wire-type group can be changed between each other without breaking compatibility:
| Wire Type | Compatible Types |
|---|---|
| Varint (wire type 0) | int32, uint32, int64, uint64, bool |
| Zigzag varint (wire type 0) | sint32, sint64 |
| 32-bit fixed (wire type 5) | fixed32, sfixed32 |
| 64-bit fixed (wire type 1) | fixed64, sfixed64 |
| Length-delimited (wire type 2) | string, bytes |
Additionally, enum types are wire-compatible with all varint types (int32, uint32, int64, uint64, bool).
optionaltorepeatedis wire-compatible forstring,bytes, andmessagefields (all use length-delimited encoding). For other types, this is incompatible.requiredtooptionalorrepeatedis compatible.optionaltorequiredis incompatible.
Changing between proto2 and proto3 syntax is not treated as incompatible. The syntax keyword is a source-level annotation; proto2 optional and proto3 fields produce identical wire bytes.
Service definitions are gRPC metadata with no wire-format impact on message serialization. The checker does not flag service changes as incompatible.
You can check whether a proposed schema is compatible with existing versions before registering it. This is useful for CI/CD pipelines or pre-registration validation.
POST /compatibility/subjects/{subject}/versions/{version}
The {version} parameter accepts an integer version number or the string latest.
POST /compatibility/subjects/{subject}/versions
When no version is specified, the proposed schema is checked against all existing versions of the subject.
{
"schema": "{...}",
"schemaType": "AVRO",
"references": []
}The schemaType field defaults to AVRO if omitted. Valid values are AVRO, JSON, and PROTOBUF. The references array is optional and is used for schemas that reference other schemas by subject.
A successful compatibility check returns:
{"is_compatible": true}An incompatible schema returns:
{"is_compatible": false}Add ?verbose=true to get detailed incompatibility messages:
POST /compatibility/subjects/my-subject/versions/latest?verbose=true
Response:
{
"is_compatible": false,
"messages": [
"BACKWARD compatibility check failed against version 1: root: reader field 'email' has no default and is missing from writer"
]
}curl -X POST http://localhost:8081/compatibility/subjects/users-value/versions/latest \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"age\",\"type\":\"int\",\"default\":0}]}"
}'{"is_compatible": true}Compatibility groups allow multiple independent schema lineages within the same subject. This is useful when a subject contains schemas that represent different major versions or different logical schema families that should not be checked against each other.
The compatibilityGroup configuration property names a metadata property key. When set, the registry only checks compatibility against existing schemas that have the same value for that metadata property. Schemas with a different value (or no value) for the property are excluded from the compatibility check.
Set a compatibility group for a subject:
curl -X PUT http://localhost:8081/config/my-subject \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"compatibility": "BACKWARD", "compatibilityGroup": "major_version"}'When registering a schema, include the group value in the metadata.properties field:
# Register v1 schema in group "1"
curl -X POST http://localhost:8081/subjects/my-subject/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schema": "{\"type\":\"record\",\"name\":\"Event\",\"fields\":[{\"name\":\"id\",\"type\":\"string\"}]}",
"metadata": {"properties": {"major_version": "1"}}
}'
# Register a completely different schema in group "2" — compatibility is not
# checked against group "1" schemas, so this succeeds even though the schemas
# are incompatible
curl -X POST http://localhost:8081/subjects/my-subject/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schema": "{\"type\":\"record\",\"name\":\"Event\",\"fields\":[{\"name\":\"event_id\",\"type\":\"int\"}]}",
"metadata": {"properties": {"major_version": "2"}}
}'Without a compatibilityGroup configured, all schemas under the subject are compared against each other regardless of metadata values.
- API Reference -- complete endpoint documentation including error codes
- Schema Types -- Avro, JSON Schema, and Protobuf parser details
- Configuration -- full YAML configuration reference
- Getting Started -- register your first schemas and test compatibility