API should have an explicit contract. That contract consists of two parts: syntax and semantics. The syntax part (schemas) is much easier to define and automate, and on its own it can already prevent many API integration failures.
A schema registry is a component that provides a single source of truth for API specifications (schemas). It can also perform automated compatibility checks, preventing one party from sending data that the receiving side cannot read.
Consider a service exposing a REST API: when you upgrade its functionality, some clients may still use the old API specification.
You want to make sure that the old data continues working with the new API. This is important because, in such integrations, there are typically many applications that send the requests (clients) and a single application that implements the API (service provider).
Therefore the reader of the data is often upgraded before some of the data writers.
This type of compatibility is called BACKWARD compatibility: old data should work with a new reader.
Backward compatibility is not limited to REST or RPC APIs. In message-based systems (such as JMS or Apache Kafka), a common pattern is that multiple clients can send command (request) messages to a service provider and optionally they receive response messages from the provider.
API schemas can be also used to derive database structures or queries. Messages sent in a topic may be consumed by a service that stores data in a data warehouse. We want to ensure that, even after schema changes, we can still query and report on historical data.
On the other hand, message brokers are often used in event-driven architectures. In this case, a producer emits events describing what happened, without knowing which or how many consumers will process them.
Here, the producer (data writer) is often upgraded before some consumers (data readers). To ensure that new data remains readable by older consumers, we need FORWARD compatibility.
The most general scenario is when both situations are possible - producers and consumers may be upgraded independently. This is known as FULL compatibility.
With JSON Schema, achieving the compatibility levels we want is not straightforward, because it was not designed with compatibility checks in mind. JSON Schema combines grammar-based and rule-based validation, but only the grammar-based aspects translate cleanly into safe parsing (deserialization).
As a result, achieving full compatibility requires non-intuitive constraints from the very beginning. You may need to anticipate future field types and enforce naming conventions (such as prefixes) for extensibility.
For a deeper explanation, see this excellent blog post by Robert Yokota: Understanding JSON Schema Compatibility.
This is why, if you are already using a message-based system, it can make sense to adopt a format that supports compatibility checks more naturally. In the Kafka ecosystem, that typically means Apache Avro, which was also the first format supported by the Confluent Schema Registry.
Schema design and compatibility can feel quite theoretical, so it helps to experiment. I created a small test project using Testcontainers to start Schema Registry automatically, along with a few examples (using Java HttpClient) that demonstrate the compatibility scenarios in both JSON Schema and Avro and the results returned from the schema registry.
All you need is Docker and Java (I use Java 25 to encourage trying the latest version with LTS). You can modify the test code and observe what happens: confluent-testcontainers on GitHub.