See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
What a schema registry does, precisely
It stores versioned schemas per subject (typically Avro, Protobuf, or JSON Schema), hands producers and consumers an id instead of the full schema on every message, and refuses to register a new version that violates the subject's compatibility rule. That refusal is the whole value: an incompatible producer deploy fails at registration rather than at 3am in a consumer.
Compatibility modes are the part worth knowing by name, and the Confluent reference is short enough to read once. BACKWARD, the default, means a consumer on the new schema can read data written with the previous one, which permits deleting fields and adding optional ones. FORWARD means a consumer on the latest registered schema can read data written with the new one. FULL is both. Each has a TRANSITIVE variant that checks against every previously registered version rather than only the immediate predecessor, and NONE turns the check off. Teams that report "the registry did not stop the break" are usually on BACKWARD when they needed FULL_TRANSITIVE.
SR=http://localhost:8081
# What rule is this subject actually enforcing?
curl -s "$SR/config/orders-value"
# Tighten it before you rely on it
curl -s -X PUT -H "Content-Type: application/json" --data '{"compatibility":"FULL_TRANSITIVE"}' "$SR/config/orders-value"
# Dry run a candidate schema in CI, before the producer deploys
curl -s -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" --data @orders-value.json "$SR/compatibility/subjects/orders-value/versions/latest?verbose=true"The four things a registry structurally cannot check
Semantics. A field renamed in meaning but not in name passes every compatibility check. If status = 3 stops meaning shipped and starts meaning partially shipped, the registry sees no change at all, and every downstream aggregate is quietly wrong.
Values. Compatibility is about shape, not content. A producer that starts emitting nulls in an optional field, or currency amounts in cents where they used to be units, breaks consumers without breaking a single schema rule.
Timeliness. The registry has no opinion about whether the topic received anything today. Freshness failures are the most common data incident, and they are entirely outside its remit.
Everything not on a topic. Warehouse tables, lakehouse tables, files, and API responses have no registry in front of them. Most analytical consumption happens after the sink has landed the data, which is exactly where a contract check runs and a registry does not reach.
Confluent Data Contracts blur the line, up to a point
Confluent extended Schema Registry with features marketed as data contracts: schema-level and field-level metadata, rulesets whose domain rules are written in Google Common Expression Language and apply on WRITE, READ or both, migration rules written in JSONata that run on UPGRADE, DOWNGRADE or both, and tags that drive downstream policy. That covers value-level validation and some semantics, which closes two of the four gaps above for streaming data.
Check the bill and the version before you plan around it. Confluent documents that schema rules are only available on Confluent Enterprise and on Confluent Cloud with the Stream Governance Advanced package, and only from version 7.4 onwards. On open-source Schema Registry, or on a lower Cloud package, the compatibility check is the whole of what you get.
What it does not change is the boundary. Everything still applies to messages on topics, at publish time, for producers using that registry. The warehouse table your analysts actually query, the daily batch load from the vendor SFTP, and the freshness of both remain outside it. That is why teams running Confluent Data Contracts still write contract files for the landed datasets.
The overlap is worth naming so you do not build it twice: CEL domain rules and a contract quality block are both value-level checks. Put the ones that must reject a single message at the registry, because that is the only place a bad record can be stopped before it lands, and put the ones that judge a whole load, row counts, distributions, freshness, in the contract, because a per-message rule cannot see them.
How to run both without duplicating work
Let the registry own the write boundary for streaming: one subject per topic, compatibility set deliberately (FULL_TRANSITIVE for topics with many independent consumers), and a compatibility dry run in the producer CI pipeline so the failure lands on the pull request.
Let the contract own the read boundary for every published dataset, streaming or batch. Generate the contract's schema block from the registry subject where one exists, so the shape is not maintained twice, and add the parts the registry has no field for: owner, semantics, quality thresholds, freshness, notice period.
Run both checks in the same pipeline. The registry check answers "may this producer deploy". The contract check answers "is what landed still what was promised". A team that runs only the first is protected against exactly one failure mode, and it is not the one that generates most incidents. The section layout for the second file is in what is in a data contract.
FAQ
What is the difference between a data contract and a schema?
Does a schema registry replace data contracts?
Which schema registry compatibility mode should you use?
What are Confluent Data Contracts?
Do you need both a schema registry and data contracts?