What Is Schema Evolution in Kafka?
How teams change the shape of their data without breaking the producers and consumers that depend on it: schemas, Schema Registry, compatibility rules, safe rollouts and the mistakes that cause outages.
Picture a restaurant that prints its menu once a week. Every waiter, cook and cashier reads the same paper. One day the owner decides to add a new column for “spice level”. If she reprints the menu and hands it out to only half the staff, chaos follows: some waiters write down spice levels the cooks cannot read, and some cooks look for a column the old menus never had. Changing a shared format is easy. Changing it without breaking anyone is the hard part.
Apache Kafka lives in exactly this situation. Dozens of teams write events into topics, and dozens of other teams read them, each on their own release schedule. The events must change over time, because businesses change: a customer gets a phone number field, an order gets a discount code, an old field is retired. Schema evolution is the discipline, and the set of tools, that lets those changes happen safely. In this guide you will learn what schemas are, how Schema Registry enforces rules, what “backward” and “forward” compatibility really mean, how to roll out changes in the right order, and how to survive the changes that cannot be made compatible.
1What Schema Evolution Means
Schema evolution is the controlled change of a data format over time, while old and new versions of the data keep working together.
Start with the basics. A message (also called an event or record) is a small package of data that a program writes to Kafka, for example “order 1042 was placed by customer 77 for 3 items”. A topic is a named log where such messages are stored in order. A schema is the written description of the shape of a message: which fields it has, what type each field is (text, number, date) and which fields are required.
Schema evolution means changing that description over time. You might add a field, remove a field, or change a type. The word “evolution” hints at the important point: the change happens gradually, in versions, while the system stays alive. Version one of the order schema might have three fields. Version two adds a fourth. Version three retires the second. At any moment, messages written under every earlier version may still exist in the topic, and programs at different release levels may be reading and writing at the same time.
Think of a paper form used by a hospital, such as a patient registration form. Over the years the hospital adds a box for an email address and drops the box for a fax number. Old forms are still in the filing cabinet. New forms are being filled today. The staff must be able to read both without confusion. The form design is the schema, each redesign is a new version, and the rules that keep old and new forms readable together are schema evolution.
The core question
Every schema change raises one question: who might be surprised? A new producer might write data that an old consumer cannot understand. A new consumer might read old data that lacks a field it now expects. Schema evolution gives precise names and automatic checks for these surprises, so a human does not have to guess.
Schema evolution is not about making changes. It is about making changes that every program in the chain can survive, in an order that everyone can follow.
2Why Kafka Needs Schema Evolution
Kafka stores bytes and knows nothing about their meaning. That freedom is powerful, and it is also the reason problems appear.
Kafka brokers treat every message as a bag of bytes. They do not check that a message contains a customer ID, and they do not know whether a price is a number or a piece of text. This makes Kafka fast and flexible, but it means the meaning of the bytes must be agreed between the writer and the reader by some other mechanism. That agreement is the schema.
Now add the reality of large companies. A topic named “orders” may be written by one team and read by ten: billing, shipping, analytics, fraud, search, recommendations, a data lake loader, and more. These teams do not release on the same day. If the producing team changes the format on Tuesday, the ten consuming teams cannot all update on Tuesday. Someone has to define what kinds of change are allowed so that the slowest team is not broken.
Independent teams
Producers and consumers release separately. A change must not require everyone to deploy at the same instant.
Long-lived data
Topics may keep messages for days, months, or forever. Old messages must stay readable next to new ones.
Replay
New consumers often start from the beginning of a topic and must understand every version ever written.
Business change
Products grow, laws change and fields come and go. A rigid format that can never change would freeze the business.
What goes wrong without it
The silent rename
A developer renames a field from “cust_id” to “customer_id” and deploys. Every consumer that expected the old name now reads an empty value. Nothing crashes. Reports quietly show zero customers, and nobody notices for three days.
The type surprise
A price changes from a whole number of cents to a decimal number of dollars. Downstream billing multiplies by the wrong amount, and invoices are wrong by a factor of one hundred.
The poison message
A new format arrives that a consumer cannot parse. The consumer throws an error on the same message every time it restarts, and the whole partition stops moving. This is often called a poison pill.
Kafka is like a postal service that delivers sealed envelopes without ever opening them. The postal workers are not responsible for what is inside. Sender and receiver must agree on the language of the letter. If the sender suddenly writes in a different language, the postal service cannot help. Schema evolution is the agreement about which languages are allowed.
3Schemas and Serialization Formats
A schema is written in a format. The three most common formats in Kafka are Avro, Protobuf and JSON Schema, and each handles change a little differently.
Before a message can be stored in Kafka, the program converts it from an in-memory object into bytes. This step is called serialization. The reverse step, turning bytes back into an object, is deserialization. The serializer follows a schema so that the bytes have a known layout.
| Format | How it identifies fields | Size and speed | Evolution style |
|---|---|---|---|
| Avro | By field name and position in the schema, resolved at read time | Very compact binary; needs the schema to read | Reader and writer schemas are compared; defaults fill gaps |
| Protobuf | By numeric field tags | Compact binary; fast | Add new tags freely; never reuse or renumber a tag |
| JSON Schema | By field name in readable text | Larger; human readable | Rules depend on whether extra fields are allowed |
Avro in plain words
Avro is a data format that stores the values without repeating the field names in every message, which makes it small. The trade-off is that the reader must know the schema to decode the bytes. Avro was designed with evolution in mind: it allows a reader to hold a different version of the schema than the writer and reconciles them using a fixed set of rules. This is why Avro has been the traditional favorite in the Kafka world.
Protobuf in plain words
Protocol Buffers, or Protobuf, is a format from Google. Every field has a number called a tag, and the bytes carry these numbers instead of names. A reader that sees a number it does not recognize simply skips that field. This makes adding new fields naturally safe. The golden rule is that a field number, once used, must never be reused for something else.
JSON Schema in plain words
JSON is the text format everyone can read, and JSON Schema is a way to describe it. It is convenient for debugging, since you can open a message and read it. It is bulkier than the binary formats. Evolution rules can be subtle, because JSON Schema can either allow or forbid extra fields, and that choice changes what counts as compatible.
Imagine three ways of sending a list of ingredients. Avro is like a shorthand list written in a fixed order, “flour, sugar, eggs”, where the reader must have the recipe card to know what each position means. Protobuf is like labeled jars with numbered stickers, where an unknown sticker is simply set aside. JSON is a full sentence such as “the flour is 200 grams”, long but readable by anyone.
There is no single winner. Avro is popular for analytics pipelines and compact storage, Protobuf for services that already use it and for strong tooling in many languages, and JSON Schema when readability and simple tooling matter most. What matters more is picking one per topic and enforcing rules on it.
4Architecture and Components: Schema Registry
Schema Registry is a small service that stores every version of every schema and refuses changes that would break compatibility.
A schema registry is a central server that keeps a catalog of schemas. Confluent Schema Registry is the best known, and there are others, such as Apicurio Registry and the AWS Glue Schema Registry, that play the same role. The registry does three jobs. It stores schemas and gives each a unique numeric ID. It tracks versions of schemas over time. And it checks each proposed new version against a compatibility rule before accepting it.
flowchart LR P["Producer Application"] --> SER["Serializer in Producer"] SER --> LB["Load Balancer and API Gateway"] LB --> SR1["Schema Registry Instance 1"] LB --> SR2["Schema Registry Instance 2"] SR1 --> ST["Internal Schemas Topic in Kafka"] SR2 --> ST SER --> T["Kafka Topic with Partitions"] T --> DES["Deserializer in Consumer"] DES --> LB DES --> C["Consumer Application"]
The components in plain words
Serializer and deserializer
Libraries inside producers and consumers. The serializer registers or looks up the schema and writes its ID into the message. The deserializer reads the ID and fetches the schema.
Registry instances
Web services that answer questions such as “register this schema” or “give me schema number 42”. Several can run together for availability.
Load balancer and gateway
Spread requests across registry instances and enforce authentication, so only trusted programs can register or read schemas.
Internal schemas topic
The registry keeps its own data in a special compacted Kafka topic. Kafka provides durability, so the registry does not need a separate database.
Subjects
A subject is a named bucket of schema versions, commonly one per topic and per key or value. Compatibility is checked within a subject.
Compatibility settings
A rule attached to a subject, or set globally, that decides which new versions are accepted.
Why the ID trick matters
Putting a full schema in every message would waste space, since the schema can be larger than the data. Instead, the registry assigns a small number to each unique schema, and every message carries just that number. A consumer that sees the number asks the registry once, remembers the answer, and decodes all later messages with that ID from memory. The registry is therefore consulted only when a new schema ID appears, not for every message, which keeps it out of the hot path.
Treat the registry as important infrastructure, not an afterthought. Run more than one instance, back it with replicated Kafka topics and protect its API. A broken registry stops new schemas from being registered and can stop new consumers from starting.
5The Journey of One Message
Follow a single order event from the producer to the consumer to see exactly where the schema and its ID are used.
The producer builds the event
The application creates an order object with its fields, such as order number, customer and total.
The serializer finds the schema ID
On the first message, the serializer asks the registry to register the schema for this subject, or to return its ID if it already exists. The registry checks compatibility and replies with a number. The serializer remembers the number.
The bytes are written with a small header
The common wire format places one zero byte, called a magic byte, then four bytes holding the schema ID, and then the encoded data. That is why each message carries only five extra bytes of overhead.
Kafka stores the message
The broker appends the bytes to a partition. It does not open, read or judge the contents.
The consumer reads the header
The deserializer sees the magic byte and extracts the schema ID from the header.
The schema is fetched once and cached
If this ID is new to the consumer, it asks the registry for the schema and keeps it in memory. Every later message with that ID is decoded without another request.
The data is decoded and reconciled
The message is decoded using the writer schema, the one it was written with, and then matched to the schema the consumer’s code expects, the reader schema. The compatibility rules make this reconciliation work.
flowchart TD
A["Producer creates event"] --> B["Serializer asks registry for schema ID"]
B --> C{"Is the schema compatible and registered"}
C -->|"No"| D["Registration rejected and producer fails fast"]
C -->|"Yes"| E["Bytes written with magic byte and schema ID"]
E --> F["Kafka stores bytes in a partition"]
F --> G["Consumer reads schema ID from header"]
G --> H["Fetch writer schema once and cache it"]
H --> I["Decode using writer schema and reader schema"]
A library book carries a small catalog number on its spine instead of the full description of the book. When a reader wants details, they look up the number once at the desk and remember it. The catalog number is the schema ID, the desk is the registry, and the reader remembers the answer so that a queue does not form at the desk for every page.
The key benefit of this design is that a bad schema is rejected when the producer tries to register it, which is before any data is written. Without a registry, bad data would reach the topic first and break consumers afterwards.
6Compatibility Types Explained
Compatibility is a rule that answers one question: can readers and writers of different versions still understand each other?
Two words are used again and again: writer schema and reader schema. The writer schema is the version used when the message was produced. The reader schema is the version the consumer’s code uses to interpret it. Compatibility describes which combinations of the two are guaranteed to work.
| Mode | Guarantee | Allowed changes | Upgrade first |
|---|---|---|---|
| BACKWARD | New reader can read data written by the previous version | Delete fields; add optional fields with defaults | Consumers |
| BACKWARD_TRANSITIVE | New reader can read data from all earlier versions | Same as above, checked against every version | Consumers |
| FORWARD | Old reader can read data written by the new version | Add fields; delete optional fields | Producers |
| FORWARD_TRANSITIVE | Old readers of all earlier versions can read new data | Same as above, checked against every version | Producers |
| FULL | Both directions against the previous version | Add or delete optional fields only | Any order |
| FULL_TRANSITIVE | Both directions against all versions | Same as above, checked against every version | Any order |
| NONE | No checks at all | Anything | Coordinate manually |
Consider a new edition of a textbook. Backward compatibility means a student with the new edition can still follow an assignment written for the old one. Forward compatibility means a student with the old edition can still follow an assignment written for the new one. Full compatibility means both students can follow each other’s assignments.
Transitive versus non-transitive
The word transitive decides how many earlier versions a new schema is checked against. Without it, a new schema is compared only with the latest registered version. With it, the new schema is compared with every earlier version in the subject. This matters whenever old data stays in the topic. If a topic keeps messages for a year, a consumer that starts from the beginning must read every version written during that year, so a check against only the latest version is not enough.
Confluent Schema Registry uses BACKWARD as its default. It is a sensible starting point, but if you replay topics from the start or keep data for a long time, consider the transitive version of your chosen mode.
7Backward Compatibility in Depth
Backward compatibility protects consumers that upgrade first. It is the most widely used mode in practice.
With BACKWARD compatibility, a consumer running the new schema can read messages written under the previous one. The natural question is what happens when the new schema has a field the old data never had. The answer is a default value. The consumer fills the missing field with the default that the new schema declares. That is why a new field must have a default in this mode: without one, the reader has no way to fill the gap.
| Change | Allowed in BACKWARD? | Why |
|---|---|---|
| Add a field with a default | Yes | Old messages lack it, so the reader supplies the default |
| Add a field without a default | No | The reader cannot fill the missing value from old data |
| Delete a field | Yes | The reader simply ignores the extra data in old messages |
| Change a type incompatibly | No | Old bytes cannot be decoded as the new type |
The upgrade order
Backward compatibility tells you that consumers should be upgraded before producers. Think about why. If a producer moved to version two first, it would start writing new data, and any consumer still on version one might not understand it. If consumers move first, they hold version two and can read both old version-one data and new version-two data as it appears. Only after every consumer has moved is it safe to upgrade the producer.
flowchart LR S1["Step 1: Register new schema version"] --> S2["Step 2: Upgrade all consumers"] S2 --> S3["Step 3: Upgrade producers"] S3 --> S4["Step 4: New data flows to ready consumers"]
Production example: adding a loyalty tier to orders
An e-commerce company wants to add an optional loyalty tier to its order events. The team registers the new schema with the field defaulting to “none”. The analytics, shipping and billing teams upgrade at their own pace and can still read every old order. When all three confirm, the ordering service starts writing the new field, with no one surprised.
Plain BACKWARD checks only against the previous version. If a topic contains three older versions, a consumer replaying from the start might meet a message that the newest schema cannot read. Use the transitive form for topics that are replayed.
8Forward and Full Compatibility
Forward compatibility protects consumers that cannot upgrade quickly. Full compatibility protects everyone, in any order.
With FORWARD compatibility, an old consumer can read messages written under the new schema. When a new producer adds a field, the old consumer does not know it exists, so it simply ignores it. When a new producer deletes a field, the old consumer would look for a value that is missing, so that field must have had a default. Forward compatibility means producers can be upgraded first, and consumers catch up later.
| Change | Allowed in FORWARD? | Why |
|---|---|---|
| Add a field (with or without a default) | Yes | Old readers ignore fields they do not know |
| Delete a field that had a default | Yes | Old readers fall back to their default |
| Delete a field without a default | No | Old readers have nothing to fall back to |
When forward compatibility fits
Many external consumers
When you cannot control when outside teams or customers upgrade, the producer needs freedom to move first.
Slow release cycles
Some consumers, such as mobile apps or batch systems, update rarely. Producers must not wait for them.
Full compatibility
FULL compatibility requires both backward and forward safety. In practice, this limits you to adding optional fields with defaults and removing optional fields with defaults. It is the most restrictive of the useful modes, and the most comfortable to live with, because producers and consumers can be upgraded in any order. Many organizations choose FULL_TRANSITIVE for their most important, widely shared topics for exactly this reason.
Strengths of FULL
- Any upgrade order works
- Safe replay of the whole topic when transitive
- Fewer coordination meetings between teams
Costs of FULL
- Very narrow set of allowed changes
- Every added field must be optional with a default
- Real breaking changes still need a separate plan
The NONE mode turns checks off. It is useful during early development or for a controlled migration, but risky in production, because nothing stops an incompatible schema from being registered.
9Safe and Unsafe Changes
Some edits are almost always safe. Others break something, whatever the setting. Knowing the difference saves outages.
| Change | Risk | Advice |
|---|---|---|
| Add an optional field with a default | Low | The standard safe move in every mode |
| Add a required field with no default | High | Breaks backward reads; avoid |
| Remove an optional field | Low to medium | Safe for the format, but confirm no consumer still relies on it |
| Remove a required field | High | Breaks forward compatibility; first make it optional |
| Rename a field | High | Looks like a delete plus an add; use aliases or add the new field first |
| Change a field type | High | Only certain widenings, such as integer to long, are safe in Avro |
| Change the meaning of a field | Very high | Nothing can detect this; create a new field instead |
| Reorder fields | Format dependent | Harmless in many cases; read the rules of your format |
The dangerous one nobody can detect
A registry checks the structure of a schema, not its meaning. If a field called “amount” switches from dollars to cents, the type stays the same, the check passes, and every downstream calculation is quietly wrong. Meaning changes need human review and documentation. The safe habit is to never repurpose an existing field. Add a new field with a new name, and retire the old one on a schedule.
Imagine a spreadsheet column headed “Weight” that has always held kilograms, and one day someone starts typing pounds into it. Every cell is still a number, so nothing looks wrong, but every total is now nonsense. The column heading is the field name, and the unit is its hidden meaning. No format checker can see a change in unit.
Write a short description for every field in the schema, including its unit and the meaning of special values. Descriptions travel with the schema, so future readers and reviewers can see what a field was meant to be.
10Internal Working: How Avro Resolves Two Versions
Avro compares the writer schema with the reader schema field by field. Knowing the rules makes every compatibility error easy to read.
When an Avro consumer decodes a message, it holds two schemas at once. The writer schema comes from the registry using the ID in the message header. The reader schema is the one compiled into the consumer’s code. Avro walks through the fields and applies a fixed set of rules to line them up.
Fields in both schemas
If a field appears in both and the types match or can be safely widened, the value is read normally.
Fields only in the writer schema
The data contains a value the reader does not know about. Avro skips it, which is why deleting fields is safe for readers on the newer schema.
Fields only in the reader schema
The data lacks a value the reader expects. If the reader schema declares a default, Avro uses it. If not, decoding fails with an error.
Type conversions
Avro allows a small set of automatic promotions, such as integer to long, long to float or double, and text to bytes. Anything else is a mismatch and fails.
Making a field optional
In Avro, “optional” is expressed by a union of null and another type, with a default of null. A union is simply a list of allowed types for one field. A field declared as a union of null and text can hold either nothing or a piece of text, and by default holds nothing. This is the standard pattern for adding a field safely: make it a union with null and give it a null default.
Renaming with aliases
Avro supports aliases, which let a reader schema say “this field used to be called something else”. A reader that knows the alias can match old data written under the earlier name. Aliases help in careful renames, but they only work for readers that have been upgraded and they behave differently across tools, so many teams prefer the safer approach of adding a new field and retiring the old one over time.
Enums need care
An enum is a field limited to a fixed list of names, such as order status values. Adding a new value is a common change, and it can break readers that meet a value they have never heard of. Avro allows a schema-level default for an enum that a reader can fall back to when it sees an unknown symbol. Without such a default, adding a value can be treated as unsafe, so plan the fallback value from the beginning.
Adding a new enum symbol often passes people’s mental checks yet breaks old readers that hold a stricter list. Give enums an explicit “unknown” symbol and a default, so future additions degrade gracefully.
11Evolving Protobuf Schemas
Protobuf evolves through numbered fields. Respect the numbers and change becomes remarkably forgiving.
In Protobuf, every field is identified by a number called a tag, and the encoded bytes carry the tag, not the name. Names can change freely in the source file without touching old data, because the number is what matters on the wire. A reader that encounters a tag it does not know skips the value. New readers that find a tag missing simply use the type’s default, such as zero for numbers or empty text.
Never reuse a number
If a field is removed, its number must never be given to a new field with a different meaning. Old data would be misread as the new field.
Reserve retired numbers
Use the reserved feature to block retired numbers and names from being reused by accident in the future.
Add fields freely
New fields with new numbers are safe. Old readers skip them and new readers use defaults on old data.
Avoid changing types
A few types are wire-compatible with each other, but most type changes corrupt data. Add a new field instead.
Think of a coat check where every coat gets a numbered ticket. The number, not the description, identifies the coat. If a coat is removed, you must not hand the same ticket number to a new coat while old customers may still hold that ticket, or someone leaves with the wrong coat. Retiring a Protobuf field number without reusing it is the same discipline.
Schema Registry applies its compatibility modes to Protobuf as well, using rules that fit the format. The details differ from Avro, for instance in how a removed field is treated, so read the documentation for your version when choosing a mode.
12Evolving JSON Schema
JSON Schema is readable but subtle. Its compatibility depends on whether unknown fields are welcome.
JSON Schema can describe an object in two styles. In an open content model, extra fields that the schema does not mention are allowed. In a closed content model, extra fields are forbidden. This single choice changes what counts as a compatible change.
| Model | Effect of adding a field | Effect of removing a field |
|---|---|---|
| Open | Old readers accept data that carries the new field | Readers that expect it may fail if it was required |
| Closed | Old readers reject data that carries the new field | Similar to open, with stricter checking |
Because of this, teams often favor the open model for topics expected to evolve, so old readers tolerate new fields. The tradeoff is that typos in field names slip through unnoticed, since anything extra is allowed. Text-based JSON is also larger on the wire than the binary formats, so high-volume topics often choose Avro or Protobuf instead.
JSON Schema evolution rules are more complex to check automatically than Avro’s. Test your intended changes against the registry’s compatibility endpoint before relying on them in production.
13Subjects and Naming Strategies
The subject is the bucket in which versions are tracked and compatibility is checked. How you name it shapes your whole design.
A subject is a named list of versions of a schema. Compatibility checks compare a new schema with earlier versions in the same subject. The naming strategy decides how the serializer picks the subject name for each message.
| Strategy | Subject name based on | Effect | Best for |
|---|---|---|---|
| Topic name (default) | The topic, plus key or value | One schema lineage per topic | Topics that hold one kind of event |
| Record name | The fully qualified record name | Same lineage across topics | Topics that mix several event types |
| Topic and record name | Both together | Lineage per record type per topic | Mixed topics that need per-topic rules |
Production example: one topic per customer, many event types
A company that stores all events for one customer in one topic, to guarantee order across event types, needs a record-based strategy. The topic then holds account-opened, payment-made and address-changed records, each with its own schema history and its own compatibility check.
Changing the naming strategy later is painful, because existing subjects, tools and consumers all assume the old convention. Decide it at the start of the project and document it.
14A Safe Rollout, Step by Step
A checklist that turns compatibility theory into a change you can deploy on an ordinary Tuesday.
Propose the change in the schema file
Edit the schema in version control, using the safe patterns: optional fields with defaults, no renames, no type changes.
Check compatibility automatically
Ask the registry to test the new schema against the subject’s current rule, as part of the build pipeline. A failure blocks the merge.
Review the meaning, not just the shape
A human reads the field descriptions and confirms nothing has changed meaning or units.
Register the schema through the pipeline
Let the deployment pipeline register the new version, not the running application, so registration is deliberate and audited.
Upgrade in the order the mode demands
Consumers first for backward, producers first for forward, any order for full.
Watch the metrics
Track deserialization errors and consumer lag through the rollout. Roll back the application if either climbs.
Clean up later
After all consumers use the new field and the old one is unused, retire the old field in a separate, later change.
Client libraries can register schemas automatically when they first produce. That is convenient in development. In production, turn it off and register through the pipeline, so nobody publishes a new schema by accident.
15Handling Breaking Changes
Sometimes the business truly needs a change that no compatibility mode allows. The answer is migration, not force.
Imagine a payment event whose amount must change from a whole number of cents to a decimal with currency. No safe evolution path exists, because the old and new types cannot be read by each other. Turning compatibility off and hoping for the best is the wrong response. The usual approach is to create a second topic for the new shape and move consumers over deliberately.
flowchart LR P1["Producer on old schema"] --> T1["Topic payments v1"] T1 --> M["Migration Job or Stream Processor"] M --> T2["Topic payments v2"] P2["Producer on new schema"] --> T2 T1 --> C1["Old Consumers"] T2 --> C2["Upgraded Consumers"]
Create the new topic and schema
Give it a clear versioned name, and register the new schema as version one of a new subject.
Translate old data into the new shape
A small stream processing job reads the old topic, converts each event, and writes it to the new topic.
Move consumers one by one
Each consuming team switches to the new topic when ready. Both topics stay alive during the transition.
Move producers
Once consumers have moved, producers write directly to the new topic and the migration job is switched off.
Retire the old topic
After a grace period, with monitoring showing no readers, the old topic is deleted or archived.
When a city changes from left-hand traffic to right-hand traffic, it cannot simply announce the change at midnight and hope. It builds a transition period with separate lanes, signs and rules. A new topic with a migration job is the same idea: a planned, visible crossing instead of a blind switch.
Some Confluent products describe “data contracts” with migration rules that transform data between major versions on the fly. Such features can reduce the need for a second topic, but they depend on your platform and version, so evaluate them against your own setup.
16Patterns for Changing Data Safely
A few repeatable moves handle nearly every real change without breaking anyone.
Expand and contract
This pattern, also called parallel change, splits a risky change into two safe ones. In the expand phase you add the new thing next to the old one. In the contract phase, much later, you remove the old thing. Between the two phases, every consumer has time to move.
| Goal | Expand phase | Contract phase |
|---|---|---|
| Rename “cust_id” to “customer_id” | Add “customer_id” as optional; producers fill both fields | After all consumers use the new name, remove “cust_id” |
| Change price from cents to decimal | Add “price_decimal”; keep “price_cents” | Remove “price_cents” once unused |
| Split “name” into first and last | Add both new fields; keep “name” | Remove “name” after consumers move |
Optional by default
Make new fields optional with sensible defaults. A required field with no default forces everything to change in lockstep, which is exactly what schema evolution is trying to avoid.
Deprecate before deleting
Mark a field as deprecated in its description, announce it to the consuming teams, and give a date. Watch usage. Delete it only when nobody reads it any more. Removing a field that a forgotten weekly report depends on is a classic way to ruin a Monday.
Envelope with a version
Wrap the payload in a small stable envelope with metadata and a payload version. The envelope rarely changes, while the payload evolves inside.
Unknown-friendly enums
Add an “unknown” symbol so new values can be added later without breaking old readers.
Additive-only culture
Agree as a team that changes are additive by default and removals are rare, scheduled events.
Contract tests
Keep small tests that read sample messages of every past version with the current code, proving nothing has broken.
17Replay, Compacted Topics, Kafka Streams and Connect
Old data keeps coming back to life. Replays, compaction and stateful applications all put pressure on old schema versions.
Replay and long retention
A topic with long retention holds messages of many schema versions side by side. A brand new consumer that starts from the earliest position will meet all of them in order. That is why the transitive compatibility modes matter: they guarantee the current reader can decode every version still sitting in the topic. If your retention is infinite, treat every version ever registered as still alive.
Compacted topics
Log compaction keeps the latest message for each key and discards older ones. It is often used for reference data such as customer profiles. Compaction does not rewrite old messages in a new schema. A compacted topic may hold, for one key, a message written years ago under version one, next to a message for another key written yesterday under version five. Readers must handle both.
Kafka Streams and state
Kafka Streams applications store running results, such as counts and totals, in state stores that are backed by internal changelog topics. Those topics contain serialized data. If you change the shape of the stored value, the application must be able to read what it wrote before. For long-lived state, evolve the stored schema with the same compatibility rules, or reset and rebuild the state deliberately during a planned migration.
Kafka Connect
Connectors move data between Kafka and other systems, using converters that talk to the registry. A source connector reading a database table may produce a new schema whenever a column is added, and a sink connector writing to a warehouse may need to add a matching column. Set the compatibility mode so that database changes such as adding a nullable column pass smoothly, and decide in advance how sinks react when a new field appears.
A family photo album spans decades. Early pages hold black-and-white prints, later pages hold digital photos. Anyone opening the album must cope with every style. Adding new pages never rewrites the old ones, and a visitor who starts from page one meets every era in turn.
Changelog and repartition topics created by Kafka Streams also carry schemas. A change to the shape of a stream can ripple into them, so include them in your compatibility thinking.
18Advantages, Disadvantages and Trade-offs
Schema management brings safety and costs a little speed. Most organizations decide the trade is well worth it.
Advantages
- Bad changes are rejected before they reach the topic
- Teams release on their own schedules
- Messages are smaller, since only an ID travels with each one
- A central catalog documents every event in the company
- Old data stays readable, so replay is safe
- Clear rules reduce arguments between teams
Disadvantages
- The registry is one more service to run and protect
- Strict modes limit which changes are possible
- Structure is checked, meaning is not
- Breaking changes still need a costly migration
- Teams must learn the rules of each format
- Schema files must be treated as real, versioned code
The main trade-offs
Strictness versus freedom
FULL_TRANSITIVE gives the safest rollout and the narrowest set of allowed changes. NONE is free but dangerous.
Readability versus size
JSON is easy to inspect and large. Avro and Protobuf are compact and need tools to read.
Central control versus speed
Pipeline-only registration is safer and slower than letting applications register on their own.
Few subjects versus many
Topic-based subjects are simple. Record-based subjects allow mixed topics but add complexity.
19Performance and Scalability
The registry is on the edge of the hot path, not in the middle of it, so it scales easily when used properly.
Clients cache schemas by ID, so the registry receives a request only the first time a client meets a schema, or when a producer starts. In steady state, millions of messages per second may flow through Kafka while the registry sees almost no traffic. This is why a small registry cluster can support very large deployments.
| Concern | What happens | What to do |
|---|---|---|
| Startup storms | Hundreds of instances restart and all ask for schemas at once | Run several registry instances behind a load balancer; stagger restarts |
| Serialization cost | Binary formats encode and decode quickly; text formats cost more | Prefer Avro or Protobuf for very high volumes |
| Message size | Only five extra bytes per message | Nothing; this is already efficient |
| Schema size | Huge schemas with thousands of fields slow the first lookup and the encode step | Split large records into smaller nested records |
| Cache misses | Rare new schema IDs cause a short registry call | Accept it; results are cached afterwards |
Production example: LinkedIn-scale event pipelines
LinkedIn, where Kafka was created, has publicly described the use of schema-based formats for its high-volume activity data. At that scale, small per-message savings, such as sending an ID instead of a full schema, add up to large savings in network and storage.
20High Availability and Reliability
A registry outage should never take down producers and consumers that are already running.
Multiple instances
Run at least two or three registry instances. One acts as the primary for writes while others serve reads, with automatic election if one fails.
Replicated schemas topic
The internal topic that stores schemas should be replicated across brokers, as with any critical Kafka topic.
Client caches
Running clients hold schemas in memory. A short registry outage affects only new schemas and new starts.
Cross-region copies
Keep schema IDs consistent across regions when data is replicated, so consumers in the second region can decode the same messages.
flowchart TD
M["Consumer reads a message"] --> Q{"Is schema ID known in local cache"}
Q -->|"Yes"| DEC["Decode immediately"]
Q -->|"No"| REG["Ask registry for schema"]
REG --> OK{"Registry reachable"}
OK -->|"Yes"| CACHE["Cache schema and decode"]
OK -->|"No"| RETRY["Retry with backoff and raise an alert"]
DEC --> NEXT["Process the record"]
CACHE --> NEXT
What to do with messages that cannot be decoded
Sometimes a message simply cannot be decoded, because of corruption, a wrong format, or an unknown schema ID. A consumer that crashes on the same message forever blocks its partition. Common practice is to catch the error, write the raw message and the error details to a separate dead letter topic, alert a person, and move on. The bad messages can then be investigated and replayed later without stopping healthy traffic.
Schema IDs are assigned by the registry. If you copy topics to another cluster but the second registry has different IDs, consumers there will fail to decode. Use a supported approach for replicating the registry, or a migration mode designed to preserve IDs.
21Security and Governance
Schemas describe your data, and controlling who can change them protects everything that depends on them.
Encrypt connections
Use TLS between clients, the load balancer and the registry, so schemas and lookups cannot be read or altered on the network.
Authenticate every caller
Require credentials for registry calls, with separate identities for applications and for humans.
Limit who can register
Allow only the deployment pipeline to register new schemas. Applications should only read. Limit changes to compatibility settings to a small group.
Read-only mode
Registry modes such as read-only prevent any new registration, which is useful for production during freeze periods or on replica registries.
Tag sensitive fields
Mark fields that hold personal data in the schema metadata, so governance tools can find and protect them.
Keep a history
Version control plus registry history give a clear record of who changed what, when, and why.
Give every subject an owner. When a change is proposed, the owning team approves it, and the consuming teams are notified. Clear ownership prevents both surprises and endless committee meetings.
22Monitoring, Logging and Metrics
A short list of signals catches almost every schema problem before customers do.
| Signal | What it tells you | Typical action |
|---|---|---|
| Deserialization error rate | Consumers meeting data they cannot decode | Halt the rollout; check the schema versions involved |
| Registration failures | Rejected compatibility checks | Fix the schema; do not loosen the rule |
| Registry latency and errors | Health of the registry service | Scale instances; inspect the load balancer |
| Dead letter topic volume | Number of undecodable messages | Investigate the producer and replay after a fix |
| Consumer lag | Whether readers keep up during a rollout | Roll back if lag climbs |
| Schema count per subject | How quickly versions are piling up | Review for churn and unneeded changes |
| Compatibility setting changes | Someone loosened a rule | Alert and review immediately |
Log every registration with the subject, version, ID and the identity that made the request. When a mystery appears at three in the morning, that log answers the first question: what changed recently? Alert on deserialization errors and dead letter volume, because these are the earliest signs that a producer and a consumer no longer agree.
23Worked Example: One Order Schema, Three Versions
Walk through a realistic sequence of changes and see which are accepted, which are rejected, and why.
An online shop publishes an order event. Version one has three fields: an order number, a customer ID and a total in cents. The subject uses BACKWARD compatibility.
Change one: add a coupon code
The marketing team wants to record a coupon code. The developer adds a field called “coupon” as a union of null and text, with a default of null. The registry checks: can a reader on the new schema read old data? Yes, because old messages lack the coupon and the reader fills in null. The change is accepted as version two. Consumers upgrade first, then the producer starts writing coupons.
Change two: make the customer ID a text value
Another developer wants to change the customer ID from a number to a text value, so that IDs can hold letters. The registry checks the change and finds that a reader expecting text cannot decode old bytes that hold a number, because a number cannot be promoted to text. The registration is rejected. The developer follows the expand and contract pattern instead: adds a new optional field “customer_ref” as text, has the producer fill both fields, and plans to retire the numeric field later.
Change three: add “customer_ref”
The optional text field with a null default is accepted as version three. Over the next weeks, each consumer switches from the old numeric field to the new text field. When monitoring shows nobody reads the numeric field, a later version removes it. Deleting a field is allowed in BACKWARD mode, so this final step is accepted too.
| Version | Change | Registry verdict | Reason |
|---|---|---|---|
| 2 | Add optional coupon with default null | Accepted | Old data lacks it; reader uses default |
| Attempt | Change customer ID from number to text | Rejected | Old bytes cannot be read as text |
| 3 | Add optional customer_ref text | Accepted | Safe additive change |
| 4 | Remove old numeric customer ID | Accepted | Deleting a field is safe for new readers |
The registry blocked the one change that would have broken consumers, and the team still reached its goal through a slower path made of small safe steps. The rules did not prevent the change, they shaped it.
24Design Patterns and Anti-patterns
A few shapes work well again and again. A few shortcuts cause most of the outages.
Patterns that work
Schema in version control
Schemas live in a repository, are reviewed like code, and are registered by the pipeline.
Compatibility gate in CI
The build fails if the proposed schema is not compatible with the registry. Problems are caught before merge.
Shared schema library
Common types such as money, address and timestamp are defined once and reused, so every event describes them the same way.
Dead letter topic
Undecodable messages are diverted for later inspection instead of stopping the pipeline.
Anti-patterns to avoid
What people do
Set compatibility to NONE so a blocked change can go through quickly.
Why it hurts
The safety net is gone for everyone. The next incompatible change reaches production unchecked, and consumers break at runtime.
Do this instead
Keep the rule in place and use expand and contract, or a new topic with a migration for true breaking changes.
What people do
Let every application register its own schema automatically in production.
Why it hurts
A developer’s experiment becomes a permanent schema version. Nobody reviews it, and the subject fills with accidental versions.
Do this instead
Turn off automatic registration and register through a reviewed pipeline.
What people do
Reuse an old field for a new purpose, such as switching a value from dollars to cents.
Why it hurts
The structure is unchanged so every check passes, and every calculation downstream becomes wrong without any error message.
Do this instead
Add a new field with a new name and retire the old one on a schedule.
What people do
Send unstructured or loosely typed blobs, such as a single text field holding a whole JSON document, to avoid dealing with schemas at all.
Why it hurts
The schema problem moves inside the blob, where no registry can check it. Consumers guess at the content, and breakage returns in a less visible form.
Do this instead
Describe the real structure in a proper schema, even if it takes a little more effort up front.
25Best Practices and Common Mistakes
A short checklist gathered from many production systems. Follow it and most schema pain never appears.
Choose a format and a mode per topic
Write the decision down. Prefer transitive modes for topics that are replayed or retained for long periods.
Give every new field a default
Optional fields with defaults are the currency of safe change.
Never rename or repurpose fields
Add a new field, migrate readers, then remove the old one.
Document fields
State the unit, the allowed values and the meaning of special values in the schema itself.
Automate checks in the pipeline
A failed compatibility test should stop a merge, not surprise you in production.
Plan the upgrade order
Follow the rule of your mode: consumers first for backward, producers first for forward.
Always have a dead letter path
A single bad message should never freeze a partition.
Mistakes seen again and again
Adding a required field
A new field without a default breaks readers of old data. Make it optional first.
Producers upgraded first under BACKWARD
The mode expects consumers first. Reversing the order gives old consumers data they cannot read.
Forgetting old data in the topic
Plain modes check only the latest version. Replaying from the start meets older versions nobody tested.
Deleting schemas
Removing a schema version that data still refers to leaves messages that nobody can decode. Avoid hard deletion.
Ignoring enums
New enum values surprise old readers. Give enums a default symbol.
Skipping contract tests
If current code has never read a sample of every past version, you do not know it can.
26Why Streams Are Harder Than Database Migrations
Changing a database table and changing a Kafka schema look alike, yet the second is far less forgiving.
Anyone who has changed a database table knows the routine: write a migration script, run it during a quiet window, update the application, and finish. The old shape of the table disappears, and every program that uses it sees only the new shape. A Kafka topic behaves differently in three important ways.
Old data stays old
A database migration rewrites existing rows. A Kafka topic is an append-only log, so old messages keep their original shape forever, or until they expire.
Many readers at once
A table usually has one owning application. A popular topic may have dozens of readers, each with its own release date and its own copy of the code.
No maintenance window
Streams run all day, so there is no quiet moment when every program stops and restarts together with the new format.
Time travel
Consumers can rewind to the past and reread old messages at any moment, so every version ever written stays in play.
A database migration is like repainting a room: you move the furniture, paint every wall, and enjoy one uniform color afterwards. A Kafka topic is like a long roll of film where every frame was shot under different conditions. You cannot repaint the frames already shot. You can only make sure that every projector, old and new, can show every frame on the reel.
This is why schema evolution is not just a nicer name for migration. It is a design approach where change is expected to be gradual, additive and reversible, and where readers must always be ready for several versions of the same data.
27Testing Schema Changes Before They Ship
A change that passes the registry can still fail in real code. A few cheap tests close that gap.
The registry answers one question: is the new schema structurally compatible? It cannot answer whether your application handles the change correctly. A layered testing plan covers both.
Local compatibility check
Before a change is even committed, run the registry’s compatibility test against a shared or local registry. Developers learn immediately that a change is blocked.
Sample messages for every version
Keep a small folder of example messages written under each past version. A unit test reads each sample with the current code and checks that the values come out right.
Round trip tests
Write a message with the new producer code and read it with the previous consumer build, and the other way around, to prove the upgrade order you plan to use is safe.
Staging replay
Replay a sample of real production messages, covering old versions too, through the new consumer in a staging environment and compare results with the current version.
Canary rollout
Upgrade one consumer instance first and watch deserialization errors and output values before moving the rest.
Production example: sample messages as a safety net
A payments team keeps one real example message per schema version in its test folder. When a developer adds a field, the automated tests decode all of them with the new code. Twice in a year, this simple habit caught a default value that silently changed the meaning of an old message, before the change reached a single customer.
Sample-message tests cost almost nothing to write and catch the most embarrassing class of bug: code that works on today’s data and fails on last year’s.
28Ownership, Communication and Deprecation
Most schema incidents are not technical failures. They are communication failures between teams.
A registry stops incompatible changes, but it cannot tell a consuming team that a field is about to disappear. The human side of schema evolution needs its own habits. The most successful organizations treat a topic like a public product with an owner, documentation and a change policy.
One named owner per subject
Every subject has a team that approves changes and answers questions. Without an owner, nobody feels responsible for the consequences.
Know your consumers
Keep a list of which teams read each topic. Consumer group names, access logs and a simple registry of readers make this list possible.
Change proposals
Post a short proposal for any non-trivial change, giving consumers a few days to comment before the change is registered.
A published calendar
When a field will go away, announce the date early and repeat the notice. Removal follows the calendar, not a whim.
A simple deprecation timeline
Suppose a team plans to retire the old “cust_id” field. In week one, it adds the new field and marks the old one as deprecated in its description. In weeks two to eight, it announces the plan and tracks which consumer groups still read the old field. In week ten, it contacts any team still using it. In week twelve, it removes the field only if usage has dropped to zero. If a critical consumer still depends on it, the date moves, and the plan is reviewed. Nobody is surprised, and nobody needs an emergency rollback.
When a city plans to close a bridge, it puts up signs months in advance, publishes detour maps and tells the bus companies. Closing without notice would strand thousands of commuters. A deprecation calendar plays the same role for data consumers.
29Moving from Untyped JSON to Managed Schemas
Many teams start with plain JSON and no registry. Adopting schemas later is possible, and it can be done without a big-bang rewrite.
Early projects often send loosely defined JSON. It is quick and it works, until three teams depend on the same topic and one of them adds a field that another team misreads. Introducing schemas at that point feels daunting, since the topic already contains months of unstructured data. A gradual path exists.
Describe what already exists
Study real messages and write a schema that matches them, marking fields that are sometimes missing as optional. The schema documents reality, warts included.
Start with a permissive mode
Register the schema and choose a mode you can live with today. Tighten it later as the schema is cleaned up.
Create a new topic with managed data
Because the old topic holds untyped messages, most teams create a fresh topic that uses the registry from the first message, and copy old data across with a translation job if needed.
Move consumers over
Consumers switch to the new topic gradually, as in any breaking migration.
Tighten the rules
Once the new topic is stable, move to a stricter, transitive mode for the long term.
Benefits of moving
- Bad data is caught at the producer
- Documentation is part of the data
- Safe changes become routine
Costs of moving
- An initial effort to describe existing data
- A migration period with two topics
- Teams must learn a few new habits
A schema that merely copies the mess, with every field optional and every type loose, gives little protection. Plan a second pass to tighten the definition once the data is flowing through the registry.
30Cost and Sizing of the Registry
The registry is small compared with Kafka itself, but a little planning avoids surprises.
Schemas are tiny text documents, and even a large company may hold only a few thousand of them. The registry therefore needs little storage. What deserves attention is the number of requests during busy moments and the health of the internal topic that stores the data.
| Factor | Typical effect | Sizing advice |
|---|---|---|
| Number of subjects | Grows with topics and record types | Thousands are fine; keep naming consistent |
| Versions per subject | Grows with change frequency | Review subjects with many versions for churn |
| Client restarts | Each restart re-fetches schemas | Stagger restarts; run several instances |
| Schema size | Large schemas slow first lookup | Split very large records into nested parts |
| Cross-region use | Extra latency for remote lookups | Run a local registry in each region |
The registry costs little to run, yet its failure blocks schema changes and new consumers. Spend on redundancy and monitoring rather than on raw capacity.
31Real-World and Industry Examples
Schema evolution appears wherever many teams share streams of data.
Kafka began at LinkedIn to carry activity data, and schema-based formats have been central to how large companies keep such data manageable. Public talks and blog posts from many companies describe the ideas below. Details differ from company to company and change over time, so treat these as illustrations of the technique.
Social networks: activity events
Events such as page views, likes and messages are produced by many services and consumed by search, feeds, recommendations and analytics. Schemas with additive changes let each team add fields for its own needs without waiting for every other team.
Ride-hailing: trip events
Ride platforms publish trip lifecycle events consumed by pricing, payments, safety and support systems. New products, such as a new ride type, add fields and enum values, and compatibility rules protect the older systems while they catch up.
Streaming video: playback events
Services such as Netflix collect playback and device events at very large scale. Schema-controlled events let device teams add new diagnostic fields, while analytics pipelines continue to run on the older shapes until they are updated.
Banking: transaction streams
Banks share transaction events across fraud, ledger, reporting and compliance systems. Because regulations require long retention and audit, transitive full compatibility and careful ownership of each schema are common choices.
Retail: orders and inventory
Retailers connect stores, warehouses and web shops through events. Adding a new sales channel means adding optional fields and enum values, and the expand and contract pattern is used to retire outdated ones without disturbing older systems.
Database change streams
Connectors that capture changes from operational databases publish a new schema each time a column is added. Compatibility rules decide whether the warehouse loader continues smoothly or stops for review.
In every example, the more teams and the longer the retention, the stricter the mode becomes. Small private topics can be relaxed. Widely shared, long-lived topics deserve the strictest rules.
32Glossary of Terms
A quick reference for the vocabulary used throughout this guide.
| Term | Plain meaning |
|---|---|
| Schema | The written description of the fields and types in a message |
| Serialization | Turning an in-memory object into bytes that can be stored or sent |
| Deserialization | Turning stored bytes back into an object a program can use |
| Schema Registry | A service that stores schema versions, hands out IDs and checks compatibility |
| Subject | A named list of schema versions within the registry |
| Schema ID | A unique number the registry assigns to each distinct schema |
| Writer schema | The schema that was used when the message was produced |
| Reader schema | The schema the consuming code expects |
| Default value | The value used when a field is missing from the data |
| Union | A field that may hold one of several listed types, often null or text |
| Alias | An alternative name that lets a reader match a renamed field |
| Transitive | Checked against every earlier version instead of only the latest |
| Dead letter topic | A holding topic for messages that could not be processed |
| Expand and contract | Adding the new thing first and removing the old thing much later |
| Poison message | A message a consumer cannot process, which blocks progress if not handled |
33Frequently Asked Questions
Quick answers to the questions that come up most when teams first manage schemas in Kafka.
34Summary and Key Takeaways
Schema evolution lets a shared stream of data change safely over time.
Key Takeaways
- Kafka stores bytes; schemas give them meaning. A schema registry stores every version and checks changes before they reach the topic.
- Compatibility modes answer who can read whom. BACKWARD protects consumers upgrading first, FORWARD protects consumers that lag, FULL protects both, and transitive modes check every earlier version.
- Additive, optional changes are the safe path. New fields should be optional with defaults, and removals should be rare, announced and scheduled.
- Never rename or repurpose fields. Use expand and contract, and remember that structure checks cannot detect a change of meaning.
- Roll out in the right order. Check compatibility in the pipeline, register through the pipeline, and upgrade consumers or producers according to your mode.
- Plan for breaking changes and failures. Use a new topic with migration for true breaks, and keep a dead letter topic, several registry instances and client caches for resilience.
- Govern and monitor. Control who may register schemas, give each subject an owner, and alert on deserialization errors and rejected registrations.