Gate Deploys on Schema Compatibility
Run a Schema Registry compatibility check in CI before merging producer changes so backward-incompatible Avro updates never reach consumers.
Task
Catch breaking event schema changes in pull requests instead of discovering them when consumers fail in production.
When to use this
A producer removes an optional-looking field from an Avro record. The registry accepts the new version because compatibility is set to NONE, and three consumer services start throwing deserialization errors minutes after deploy.
Prerequisites
- A Confluent Schema Registry or Apicurio instance reachable from CI
- Avro schemas stored in the repository
- A CI job with network access and registry credentials
Steps
1. Set the subject compatibility mode to BACKWARD so new schemas can read data written by the previous version.
curl -s -X PUT \
-H "Content-Type: application/json" \
--data '{"compatibility":"BACKWARD"}' \
http://schema-registry.internal:8081/config/orders-value
Expected output: {"compatibility":"BACKWARD"}
2. Ask the registry whether the candidate schema is compatible before registering it.
curl -s -X POST \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
--data @schemas/orders-value.json \
http://schema-registry.internal:8081/compatibility/subjects/orders-value/versions/latest
Expected output: {"is_compatible":true} when only optional fields with defaults were added.
3. Fail the CI job when the registry reports an incompatible change.
result=$(curl -s -X POST \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
--data @schemas/orders-value.json \
http://schema-registry.internal:8081/compatibility/subjects/orders-value/versions/latest)
echo "$result" | grep -q '"is_compatible":true' || {
echo "Incompatible schema change detected"; exit 1; }
Expected output: A pull request that removes a required field without a default fails with a clear message.
4. Register the schema only after the compatibility check passes, then tag the release.
curl -s -X POST -H 'Content-Type: application/vnd.schemaregistry.v1+json' --data @schemas/orders-value.json http://schema-registry.internal:8081/subjects/orders-value/versionsExpected output: A new version id is returned and the subject now points at the compatible schema.
Troubleshooting
Problem
The compatibility endpoint returns 404 for the subject
Solution
Register the first version of the subject before running checks; the endpoint requires an existing subject and version.
Problem
The check passes but a consumer still fails
Solution
Structural compatibility does not cover semantics; run a consumer contract test with an old consumer build against the new schema.
Limitations
- BACKWARD compatibility allows removing fields only when consumers tolerate missing values; choose FULL for stricter producer and consumer safety.
- The registry is a network dependency in CI; cache the schema and fail closed if it is unreachable.
Compatibility is a contract
A schema registry stores versions and enforces a compatibility mode. BACKWARD means a new schema can read data produced with the previous one, which is the common choice for rolling upgrades where consumers lag producers. FULL additionally requires the previous schema to read data produced by the new one. Setting compatibility to NONE defeats the registry entirely.
Check before register
The /compatibility endpoint is a dry run: it validates the candidate against the latest version without creating one. Run it in CI on every pull request that touches schemas/. The check must fail closed: a network error or a missing subject should stop the build, not be treated as success. Parse the JSON response instead of relying on the HTTP status alone.
curl -s -X POST \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
--data @schemas/orders-value.json \
http://schema-registry.internal:8081/compatibility/subjects/orders-value/versions/latest
Think about evolution
Adding a field with a default is backward compatible. Removing a field is compatible only if consumers can handle its absence. Renaming a field is a removal plus an addition and breaks consumers that read the old name. Changing a type from int to string is not compatible. Encode these rules in reviewer guidance so authors design changes that pass before CI tells them.
Beyond structure
Compatibility checks cannot detect a changed unit, a swapped enum meaning, or a field that is now always null. Pair the registry gate with a consumer contract test in staging. Publish the schema version in the event header so consumers can reject unsupported versions explicitly, and keep old schema versions retrievable for replay.