Request and Response Protocol Versions in Apache Kafka
How old and new clients and brokers understand each other: the wire format, API keys, per-API version numbers, the ApiVersions handshake, flexible encodings, feature flags and the rules that make rolling upgrades possible.
Imagine an international airport where pilots from every country talk to the control tower. Some pilots trained decades ago and use an older phrasebook. Others arrived last week with a brand-new edition. The tower cannot shut down for a day to teach everyone the new edition, and it cannot refuse the old pilots either. So before any serious conversation begins, each pilot and the tower quickly agree: “Which edition of the phrasebook shall we both use today?”
Apache Kafka lives in exactly this situation. Thousands of applications, written in many languages and built in many years, talk to brokers that are upgraded on a different schedule. The mechanism that keeps this crowded conversation orderly is the request and response protocol versioning system. This tutorial explains it from the first principles to the advanced details that architects need when planning upgrades and debugging strange compatibility errors.
1Why Kafka Needs Protocol Versions
Before looking at any bytes, we must understand the real-world problem that versioning solves.
A distributed system never upgrades all at once
A Kafka cluster may hold dozens of brokers. Around it may sit hundreds of producers, consumers, stream processors, connectors, monitoring agents and administrative tools. These pieces are owned by different teams, written in different programming languages, and deployed on different days. It is almost never possible to stop everything and upgrade everything at the same moment.
That means, at any given time, a cluster is likely to be talking to software of several ages. A broker from this year may be serving a client library from three years ago and a command-line tool from last month. Everyone still expects messages to flow.
Think of a train network. New trains with modern doors arrive constantly, but old platforms remain in use. The railway cannot close every station to rebuild all platforms overnight. Instead, trains and platforms follow agreed standards so that old and new can still work together while upgrades happen gradually.
What could go wrong without versioning
Suppose the Kafka team wants to add a new field to the request that fetches messages, for example a field telling the broker which transaction isolation level the consumer wants. If the client and broker simply assumed the newest format, an old client would send a request without the field, and a new broker would misread the following bytes as if they belonged to that field. The result would be garbage, crashes or silent corruption.
Going the other way is just as dangerous. A new client sending an extra field to an old broker would confuse it, because the old broker has no idea what the field means or how long it is.
Layout drift
Adding, removing or reordering fields changes the meaning of every byte that follows unless both sides know exactly which layout is in use.
Behavior drift
Even with an identical layout, the meaning of a field can change between releases, so both sides must agree on the behavior too.
Unknown capabilities
A client does not know in advance what the broker it just connected to can do. Guessing wrong causes errors.
Upgrade coordination
Forcing every application and broker to change together would make Kafka unusable in real organizations.
The solution in one sentence
Every request type in Kafka carries an explicit version number, every broker advertises the range of versions it understands for each request type, and every client picks a version that both sides support before doing real work. That simple agreement, repeated for every request type, is the heart of Kafka’s compatibility story.
Protocol versions describe how requests and responses are shaped on the network. Message format versions describe how records are stored inside a batch. They are related, but they are separate ideas, and we will study both.
2The Kafka Wire Protocol in Plain Words
Kafka does not use a web-style protocol. It has its own compact, binary request and response conversation over ordinary network connections.
A custom binary protocol over TCP
Kafka clients open a normal network connection to a broker, typically using the TCP transport, optionally wrapped in TLS encryption. Over this connection they exchange messages in a purpose-built binary format. It is not text, it is not JSON, and it is not HTTP. Binary formats are much smaller and much faster to parse than text formats, which matters when a broker handles hundreds of thousands of requests each second.
Compare a hand-written letter with a pre-printed form that has tiny numbered boxes. The letter is friendly to read but slow to process. The form is unreadable to a stranger but a clerk can process a thousand of them in an hour because every box always means the same thing.
The request and response rhythm
The conversation follows a strict pattern. The client sends a request. The broker sends back exactly one response for that request. There is no unsolicited chatter from the broker. Requests on one connection are answered in the order they were received, which keeps the protocol simple and predictable.
A client may send several requests without waiting for earlier answers. This is called pipelining. It improves throughput because the network does not sit idle while waiting for each individual answer.
Size-prefixed framing
Every request and every response begins with a number that says how many bytes follow. This is like putting a note on the front of each parcel that says “this parcel weighs 300 grams”. The receiver reads the number, then reads exactly that many bytes, and knows the parcel is complete. Without this, a reader would not know where one message ends and the next begins on a continuous stream of bytes.
Size
A four-byte number announcing the length of the rest of the request.
Header
A small block that says which kind of request this is, which version of it this is, and a number used to match the eventual response.
Body
The actual content, whose exact shape depends on the request type and the version named in the header.
Why the version sits in the header
The receiver must know how to read the body before reading it. Since the body’s layout depends on the version, the version is placed in the header, which itself has a small, stable shape. The receiver reads the header first, looks up the request type and version, chooses the matching layout, and then parses the body accordingly.
Kafka does not have one protocol version for the entire connection. It has a separate version number for each kind of request. A single connection may carry a produce request at one version and a fetch request at a very different version.
3API Keys: The Menu of Requests
Every kind of request in Kafka has a small numeric identifier called an API key.
Think of a restaurant where each dish on the menu has a number. Instead of saying “one grilled chicken with rice and vegetables”, a customer can say “number twelve”. The kitchen knows exactly what that means. Kafka does the same: the first meaningful item in a request header is the API key, a small number that tells the broker what the client wants.
Produce
Sends batches of messages from a producer to the leader of a partition so they can be appended to the log.
Fetch
Reads messages from a partition. Consumers use it, and so do follower replicas copying data from leaders.
Metadata
Asks which topics and partitions exist and which broker leads each one. Clients use this to route requests correctly.
ApiVersions
Asks a broker which request types and which version ranges it supports. This is the negotiation request.
ListOffsets
Finds offsets by time or by position such as earliest or latest, used when starting or resetting consumers.
Group coordination requests
Join group, sync group, heartbeat and leave group requests that let consumers in a group share partitions.
Offset commit and fetch
Store and retrieve how far a consumer group has read in each partition.
Administrative requests
Create topics, delete topics, alter configurations, manage access rules and describe the cluster.
Transaction requests
Initialize producer identity, add partitions to a transaction and commit or abort it.
Authentication requests
Handshake and authenticate requests used by SASL security mechanisms.
Internal APIs
Not all requests come from clients. Brokers and controllers also talk to each other with their own requests. Replica followers use fetch requests to copy data. Controllers exchange requests to elect leaders, register brokers and share metadata. Some of these APIs are marked for internal use only, and their availability to ordinary clients may be restricted. They still use the same version mechanism.
API keys never change meaning
Once a number is assigned to a request type, it belongs to that type forever. If Kafka removes a request type, its number is retired and not reused for something else. This stability is important, because an old client using API key one must always mean the same request as it did years ago, even if the broker has a completely different set of newer request types.
Emergency telephone numbers stay the same for decades because everyone depends on them. Reassigning a well-known number to a different service would cause chaos. Kafka treats API key numbers the same way.
The list of API keys has grown steadily over the years as Kafka gained transactions, quotas, security, administration features and controller-quorum requests. Each new feature tends to arrive as a new request type or as a new version of an existing one.
4Request and Response Headers
The header is the small label on every message. It, too, has versions.
What the request header carries
API key
Which kind of request this is.
API version
Which shape of that request the body follows.
Correlation identifier
A number chosen by the client and echoed back in the response, so pipelined answers can be matched to their questions.
Client identifier
A human-readable name for the application, useful for quotas, logging and metrics.
A courier label shows what type of parcel this is, which handling rules apply, a tracking number that will appear on the delivery receipt, and the sender’s name. The correlation identifier in Kafka is that tracking number.
Header versions
The header format itself has evolved. The earliest header held only the four fields above. A later header version, introduced together with the flexible encoding described in a later chapter, added a section at the end for optional tagged fields. So there are several header versions, and the header version used for a request is decided by the request’s own version.
| Header | Contents | Used when |
|---|---|---|
| Request header, older style | API key, version, correlation identifier, client identifier | Older, non-flexible request versions |
| Request header, flexible style | Same as above plus optional tagged fields | Newer, flexible request versions |
| Response header, older style | Correlation identifier only | Older, non-flexible response versions |
| Response header, flexible style | Correlation identifier plus optional tagged fields | Newer, flexible response versions |
The special case of ApiVersions
There is one deliberate exception. The response to the ApiVersions request always uses the oldest, simplest response header, even when the request itself uses a newer version. The reason is practical: the ApiVersions exchange is the very first conversation, so the client cannot yet know what the broker supports. Using the simplest header for this one response means even a very old client can read at least the essential part of the answer.
People writing their own client libraries sometimes apply the newer response header to the ApiVersions response and then fail to read it. Remember this special rule whenever you implement or debug a protocol client.
5Every API Has Its Own Version Line
Versions in Kafka are not global. Each request type keeps its own counter.
Independent counters
Imagine a large company where every department publishes its own policy handbook. The finance handbook may be on edition nine, the travel handbook on edition three, and the security handbook on edition twelve. There is no single edition number for the whole company, because each department changes its rules at its own pace.
Kafka works the same way. The fetch request might be at a high version because it has been refined many times, while a rarely used administrative request might still be on its first or second version. Each version number is meaningful only together with its API key.
Software libraries, apps on a phone and operating systems all have separate version numbers. Saying “version 5” means nothing without saying “version 5 of what”. A Kafka version number is always “version N of API key K”.
Why not one number for everything
Advantages of per-API versions
- A change to one request does not force a version bump for unrelated requests
- Clients only need to upgrade the specific behaviors they actually use
- Broker developers can evolve one feature without touching others
- Compatibility can be reasoned about one request at a time
Costs of per-API versions
- The total number of combinations is large and must be tracked carefully
- Documentation and testing must cover many separate version ranges
- Client libraries must implement version selection for each request type
- Diagnosing problems requires knowing which request and which version were involved
What a version range means
For each API key, a broker supports a contiguous range of versions, from a minimum to a maximum. For example, a broker might support versions three through nine of one request. A client that only understands versions zero through two cannot use that request with this broker. A client that understands versions five through twelve can use versions five through nine.
| Broker supports | Client supports | Overlap | Result |
|---|---|---|---|
| 3 to 9 | 0 to 2 | None | Client cannot use this request against this broker |
| 3 to 9 | 5 to 12 | 5 to 9 | Use version 9, the highest shared |
| 0 to 5 | 0 to 12 | 0 to 5 | Use version 5, the highest shared |
| 0 to 12 | 0 to 12 | 0 to 12 | Use version 12 |
A version range is a promise about what a party can read and write for one API key. Both the client and the broker publish or embed their own range, and the negotiation is simply finding the overlap.
6The ApiVersions Handshake
The very first request on a fresh connection is usually a polite question: what can you do?
The conversation step by step
Connect
The client opens a network connection to a broker, applying encryption if configured.
Ask for capabilities
The client sends an ApiVersions request, usually at the newest version it knows.
Receive the menu
The broker replies with a list: for every API key it supports, the minimum and maximum version it will accept.
Compute the overlap
For every API key the client cares about, it finds the highest version supported by both sides.
Proceed with real work
All later requests on that connection use the negotiated versions. Security handshakes and data requests follow.
sequenceDiagram
participant C as Client
participant B as Broker
C->>B: Open connection
C->>B: ApiVersions request
B-->>C: List of API keys with min and max versions
C->>C: Choose highest shared version per API key
C->>B: Metadata request at chosen version
B-->>C: Metadata response
C->>B: Produce request at chosen version
B-->>C: Produce response
Two people meet at a conference. Before discussing technical details, they ask, “Which language are you most comfortable in?” They discover they both speak English fluently and one also speaks French. They choose English for the deep conversation because it is the best language they share.
What the response contains
The ApiVersions response is essentially a table. Each row says, for one API key, the lowest and highest version this broker supports. In newer versions of the response, the table also includes information about broker features, such as the cluster-wide metadata version and other finalized feature levels. We cover those later.
Software name and version information
A later version of the ApiVersions request lets the client tell the broker the name and version of its client software, for example “the standard Java client, release such-and-such”. Brokers can expose this in metrics and logs. This is very valuable in real operations, because it lets administrators ask “which applications are still using an ancient client library?” and plan upgrades with evidence.
Finding outdated clients
Before removing support for old protocol versions, an operations team can look at broker metrics that group connections by client software name and version, then contact the owners of anything too old.
Debugging odd behavior
If one application behaves differently from the rest, knowing its client library version often explains why.
The broker advertises what it can accept. It is the client library that computes the overlap and decides which version to send. If the client’s decision is wrong, the broker will reject the request.
7What If the Client Is Newer Than the Broker
The negotiation has a clever trick for the case where even the ApiVersions request itself is too new for the broker.
The awkward first message
A client always begins with ApiVersions. But suppose the client is very modern and sends the newest version of ApiVersions to a very old broker that has never heard of that version. The old broker cannot parse the request. If it simply dropped the connection, the client would have no clue why.
The graceful fallback
Kafka handles this with a special rule. When a broker receives an ApiVersions request whose version it does not support, it replies using the oldest response format, with an error code meaning “unsupported version”, and it still includes its table of supported versions in that reply. The client reads the error, looks at the table, sees the highest ApiVersions version the broker does support, and simply asks again using that older version. This is why the ApiVersions response always uses the oldest header: even the most ancient broker can produce it.
sequenceDiagram
participant C as New client
participant B as Old broker
C->>B: ApiVersions at newest version
B-->>C: Error unsupported version, plus supported table in oldest format
C->>C: Note broker maximum for ApiVersions
C->>B: ApiVersions again at older version
B-->>C: Full list of supported API versions
C->>B: Normal requests at negotiated versions
A traveler greets a shopkeeper in a modern slang the shopkeeper does not understand. The shopkeeper says, “Sorry, I only know the old greeting,” and helpfully speaks it slowly. The traveler nods, repeats the greeting in the old style, and the conversation continues smoothly.
Older brokers with no ApiVersions at all
Very early Kafka brokers, from before the negotiation mechanism was invented, do not understand the ApiVersions request at all. Modern clients that want to support such ancient brokers traditionally need a fallback: either a manual setting telling the client which broker release to assume, or a guess based on trial and error. Some client libraries offered exactly such a “broker version fallback” setting for this reason. Today this situation is rare, and newer Kafka releases have deliberately dropped support for the oldest protocol versions, as we discuss later.
What happens to unsupported requests in general
If a client sends some other request at a version the broker does not support, the broker will typically return an error indicating the version is unsupported. Depending on the type of request and the broker’s design, it may also close the connection. Well-written clients avoid this entirely because they only choose versions from the negotiated overlap.
If your application logs mention an unsupported version error, the first suspicion is a mismatch between the client library and the broker, or a custom client that skipped negotiation. Compare the versions each side supports for the failing request.
8Flexible Versions and Tagged Fields
A major redesign of the encoding made future changes cheaper and safer.
The old problem: every change needs a new version
In the classic encoding, adding even a small optional field meant creating a whole new version of the request, and every client and broker had to know about it. The version numbers rose quickly, and old brokers that did not know a new field could not tolerate it at all.
The idea: optional extras that can be skipped
A Kafka improvement proposal introduced flexible versions. In a flexible version, every message ends with a section of tagged fields. A tagged field has a numeric tag, a size and its data. A reader that recognizes the tag reads it. A reader that does not recognize the tag knows exactly how many bytes to skip, so it can safely ignore the field and continue with everything else. This turns many changes into harmless, ignorable additions.
Picture a parcel with an extra pocket on the outside for optional notes. Each note carries a label and a length. A receiver who understands a label reads the note. A receiver who does not simply leaves that note in the pocket. The main contents of the parcel stay intact either way.
Compact encodings
Flexible versions also switched to more compact ways of writing lengths. Instead of writing every string and array length in a fixed number of bytes, they use variable-length integers, which take a single byte for small values. This saves space when there are many small strings and arrays, which is very common in Kafka requests that list many topics and partitions.
| Aspect | Classic versions | Flexible versions |
|---|---|---|
| String and array lengths | Fixed-size numbers | Compact variable-length numbers |
| Optional additions | Require a new version | Can often ride as tagged fields |
| Message ending | Body ends with the last field | Body ends with a tagged fields section |
| Header | Basic header | Header with tagged fields |
| Size on the wire | Larger for lists | Smaller for lists |
Where tagged fields help most
Optional diagnostics
Extra information that helps monitoring but is not essential for correctness can be added without disturbing older readers.
Rarely used features
A field needed by only a few callers does not need to bloat every request for everyone else.
Experiments and gradual rollouts
New information can be introduced carefully and, if unused, is ignored by receivers that have not adopted it.
Tagged fields are for information that a receiver can safely ignore. If a change alters the meaning of existing fields, or if the receiver must understand the new field to behave correctly, a new version of the API is still required.
9Message Format Versions Inside Record Batches
The records inside a produce or fetch request have their own format version, separate from the request version.
Two layers of versioning
Picture a shipping container filled with boxes. The container has a standard shape so that cranes and trucks can handle it. The boxes inside have their own packing layout. Kafka has this same double layer. The request is the container, and the records are the boxes. The request version says how the container is laid out. The message format version says how the boxes inside are packed.
The historical generations
Format zero
The original design. Each message has a key, a value and basic integrity check information, but no timestamp.
Format one
Added timestamps to messages, enabling time-based retention and time-based lookups.
Format two
Introduced record batches as the main unit, headers on records, compact encoding of fields inside a batch, and the metadata needed for idempotent and transactional producers.
The number that identifies this generation is stored in a small field within each batch, historically called the magic byte. A reader looks at it first and then knows exactly how to interpret the rest.
Down-conversion for older consumers
If an old consumer asks for messages using an old version of the fetch request that only understands an older message format, the broker may need to convert stored messages to that older layout on the fly. This is called down-conversion. It costs CPU and memory, and it prevents the broker from using its most efficient path for sending data straight from disk to network, called zero-copy. A cluster full of old consumers can therefore be noticeably slower than one full of modern ones.
A restaurant that cooks in bulk can serve modern guests quickly. If some guests demand an old-fashioned recipe, the kitchen has to prepare each of their plates separately, which is much slower and uses more staff. The old guests are fully served, but the kitchen pays for it.
| Layer | What it describes | Where the number appears | Evolves when |
|---|---|---|---|
| Request version | Layout of the request and response bodies | Request header | Fields of an API change |
| Message format version | Layout of records in a batch | Inside each record batch | Storage layout of records changes |
| Feature or metadata version | What the cluster as a whole is allowed to do | Cluster metadata | Operators finalize a new level |
Recent Kafka releases removed support for the two oldest message formats and their related broker settings, which greatly reduces down-conversion and removes a class of upgrade mistakes. Always check the release notes of your target version for the exact cut-off.
10How a New Version Is Born
Changing the protocol in Kafka is a deliberate, public and carefully reviewed process.
The improvement proposal process
Kafka changes that affect the protocol, public interfaces or compatibility are proposed in a written document called a Kafka Improvement Proposal, usually shortened to KIP. The document describes the problem, the proposed new fields or requests, how old and new versions will coexist, and how upgrade and downgrade will behave. The community reviews it, discusses it openly and votes before the change is accepted.
Write the proposal
A contributor documents the need, the new request or new fields, and the compatibility plan.
Discuss and vote
The community reviews the design, raises concerns about compatibility and finally votes.
Describe the schema
The request and response are described in a schema file that lists each field and the range of versions in which it exists.
Generate the code
Build tooling reads the schemas and generates the reading and writing code for every version, so that hand-written parsing bugs are avoided.
Implement behavior
Broker and client code is updated to produce and handle the new version, while continuing to serve all older supported versions.
Release and document
The new version appears in a release, and the protocol documentation and upgrade notes are updated.
Schemas as the single source of truth
Each request and response type is defined in a small structured schema. For every field, the schema states the versions in which the field exists, whether it may be empty in certain versions, and whether it is an optional tagged field. The client and broker code that reads and writes the bytes is generated from these schemas. This keeps every version consistent and lets developers see, at a glance, the entire history of an API.
A building’s blueprint is the single source of truth. Plumbers, electricians and carpenters all work from the same drawings, so their work fits together. If each trade improvised from memory, walls and pipes would collide.
Because fields are annotated with the versions in which they appear, the schema for any API is like a timeline. You can see when a field was added, when it became optional and when it was removed.
11Compatibility Rules: What May Change and What May Not
Freedom to evolve depends on a few strict rules that Kafka developers follow.
Backward and forward compatibility
Backward compatibility means new software can still work with old partners. Forward compatibility means old software can tolerate the presence of newer partners. Kafka aims for both at the level of clients and brokers within supported ranges: a new client should be able to talk to a reasonably old broker, and an old client should be able to talk to a newer broker, as long as the versions they share still exist.
A modern power socket that still accepts old plugs is backward compatible. A modern plug that still fits in older sockets is forward compatible. The most comfortable travel adapters manage both.
The rules of the road
Never change an existing version
Once a version has been released, its layout and behavior are frozen. Any change, however small, becomes a new version.
Keep old versions working
Brokers continue to understand older versions of each API for a long time, so older clients keep working.
Add fields only in new versions
New fields exist only from the version that introduces them. Older versions of the request never carry them.
Provide sensible defaults
When an old client sends a request lacking a newer field, the broker must behave as if the field held a sensible default that matches the old behavior.
Deprecate slowly, remove deliberately
Removal of old versions is announced in advance, tied to a major release and explained in the upgrade notes.
Document every change
Every change is described in the proposal, the release notes and the protocol documentation.
Examples of changes and their consequences
| Kind of change | Needs a new version? | Why |
|---|---|---|
| Add an ignorable optional detail in a flexible version | Often no, may use a tagged field | Readers that do not know it can skip it |
| Add a required field whose absence changes behavior | Yes | Old readers would misparse or misbehave |
| Change the meaning of an existing field | Yes | Same bytes would mean something different |
| Remove a field | Yes | Layout shifts for everything after it |
| Add a new error code | Depends on how clients react | Old clients may not understand a new code |
| Introduce a new request type | New API key at its first version | Old brokers will simply not list it |
Even when the layout stays the same, the broker’s behavior can differ for different versions of a request. A client that chooses an older version may see older semantics, such as a different error code or a simpler response, while a newer version may unlock richer behavior.
12Bidirectional Client Compatibility
The negotiation mechanism opened the door to a very friendly policy: new clients and old brokers can work together, and so can old clients and new brokers.
The historical turning point
In the early days of Kafka, a client library could only talk to brokers of the same age or newer. Upgrading a client required upgrading the brokers first. Once the ApiVersions request was introduced and client libraries learned to use it, a new client could look at what an older broker supported and speak accordingly. Newer clients could now talk to a broad range of older brokers, and older clients continued to talk to newer brokers, as newer brokers kept the older versions available.
flowchart LR
OC["Older client library"] -->|"uses older shared versions"| NB["Newer broker"]
NC["Newer client library"] -->|"uses older shared versions"| OB["Older broker"]
NC -->|"uses newest shared versions"| NB
OC -->|"uses its own versions"| OB
What this policy gives you
Freedom in upgrade order
Teams can upgrade brokers without waiting for every client to be ready, and can upgrade individual applications without waiting for the brokers.
Safe experimentation
A team can try a newer client library with an older test cluster to see whether the application behaves well.
Multi-cluster environments
Applications that connect to several clusters of different ages, such as a mirroring tool, can use one client library that adapts to each cluster.
Limits of compatibility
Compatibility is broad but not infinite. New features require new request versions, so an old broker cannot support a feature that the client needs a newer version to express. For example, if a client wants a feature that only exists in a later version of some request, and the broker’s range stops before that version, the client must either do without the feature or fail with a clear error. The most modern behaviors are only available when both sides are modern enough.
| Situation | Result |
|---|---|
| Old client, new broker | Works using older shared versions; newest broker features are not used |
| New client, old broker | Works using the highest versions the old broker understands; some new features unavailable |
| New client, new broker | Works with newest versions and all features |
| Very old client, very new broker whose oldest versions have been removed | May be refused; client must be upgraded |
When a specific feature does not work, ask first whether the highest shared version of the relevant request is new enough to include it. This one question explains a large share of “why does this work on cluster A but not cluster B” puzzles.
13Removing Old Versions: Lifecycle and Deprecation
Supporting every version forever would become a heavy burden. Kafka retires old versions carefully.
The cost of keeping old versions alive
Every supported version is code that must be maintained, tested and secured. Over many years, the number of supported combinations grows very large. Developers must reason about behaviors from many eras, and every new feature must also remain compatible with all the historic paths. This slows development and can hide bugs.
A bus company that must keep spare parts and trained mechanics for every model it has ever operated will find its workshop overflowing. Eventually it announces that models older than a certain year will retire, with plenty of notice for passengers to move to newer buses.
Announced, staged removal
Propose the removal
A proposal explains which old versions will be dropped, why, and what the minimum supported client will be afterward.
Deprecate in advance
Documentation and release notes warn that the versions will disappear in an upcoming major release. Some releases also log warnings when deprecated versions are used.
Tie removal to a major release
The actual removal happens in a major release, so operators expect breaking changes and read the upgrade guide carefully.
Publish the new floor
The documentation states the oldest client versions that will still work against the new brokers.
A real example of a floor being raised
The Kafka 4.0 release, which also removed ZooKeeper, took a big step here. It dropped support for many of the oldest protocol versions of the main request types, raising the minimum client version that can talk to the broker. The exact minimum differs per request type, but the practical message was clear: clients from many years ago need to be upgraded before, or together with, moving to the newest brokers. Anyone planning such an upgrade should check the official upgrade guide for their exact target release.
Before a major broker upgrade, inventory the client libraries in use across your organization. A single forgotten batch job using a very old library could stop working the moment the old versions are removed.
Client inventory by metrics
Use broker-side metrics that record client software name and version to list every library version currently connected, then chase owners of the oldest ones.
Staging rehearsal
Run representative old clients against a staging cluster on the target release and record any failure before touching production.
14Feature Flags and Finalized Features
Protocol versions tell you what a single broker can understand. Feature flags tell you what the whole cluster has agreed to use.
The gap that versions alone leave
During a rolling upgrade, some brokers run new software while others still run old software. A new broker may be capable of a new behavior, but if it started using it immediately, the still-old brokers could not cooperate. The cluster needs a way to say “everyone has been upgraded, now we may all start using the new behavior together.”
Supported versus finalized
Kafka distinguishes two ideas. A supported feature range describes what one broker’s software is capable of. A finalized feature level describes what the cluster has collectively decided to use right now. Brokers advertise the supported range when they register. Operators then raise the finalized level, and the cluster refuses to raise it beyond what every member supports. The finalized information is stored in the cluster metadata and is also returned by the ApiVersions response, so clients can see it.
A group of colleagues each install a new version of a shared calendar app. Everyone can use the new features, but the team agrees to switch on the shared new features only when every person has finished updating. Until then the team keeps using the older common feature set.
The cluster-wide metadata version
The most important finalized feature is the cluster metadata version. In modern KRaft-based clusters, it controls the format of internal metadata records and which cluster behaviors are enabled. It has taken over much of the role that older broker-to-broker protocol settings used to play in ZooKeeper-based clusters, where an operator would edit a configuration setting on each broker in stages.
| Idea | Question it answers | Who decides |
|---|---|---|
| Request API version range | Which request shapes can this software read? | The software release |
| Supported feature range | Which cluster features is this broker capable of? | The software release |
| Finalized feature level | Which cluster features is the whole cluster using now? | The operator, checked by the controller |
Upgrade software first, let it run and prove itself, and only then finalize the new feature level. This gives you a safe window in which you can still roll the software back.
15Broker-to-Broker and Controller Protocols
Clients are not the only speakers. The servers of the cluster talk to each other using the same versioning idea.
Replication uses ordinary requests
When a follower replica copies data from a partition leader, it does so by sending fetch requests, just like a consumer, with a few extra details identifying it as a replica. The follower and the leader must therefore agree on a fetch version, and the same negotiation ideas apply. A cluster with brokers of different ages during an upgrade relies on this to keep replication healthy.
Controller communication
Brokers register with the active controller, send heartbeats and report leadership changes using dedicated request types. Controllers talk to each other to elect a leader and replicate the metadata log. All of these are ordinary versioned APIs with their own key numbers and version ranges, and the same rules of compatibility and deprecation apply to them.
Client to broker
Produce, fetch, metadata, group and administrative requests, negotiated per connection by the client.
Follower to leader
Replication fetches that copy partition data between brokers.
Broker to controller
Registration, heartbeat and partition state change requests.
Controller to controller
Vote, fetch and epoch messages that keep the controller quorum in agreement.
In a hospital, doctors talk to patients, doctors talk to nurses and nurses talk to the pharmacy. Each conversation follows its own agreed forms, but they all rely on the same idea of standard, labeled documents that each department can read.
Why internal upgrades need extra care
If a client fails to understand a broker, one application suffers. If a broker fails to understand another broker, replication or coordination may fail for many partitions at once. That is why internal protocol changes are guarded by the feature-finalization mechanism from the previous chapter. New internal behaviors are only switched on after every member of the cluster has been upgraded and the operator has explicitly finalized the new level.
A cluster that has brokers of two different releases is in a transitional state. Keep that window short, monitor it closely and avoid making unrelated changes while it lasts.
16A Walkthrough: An Older Client Meets a Newer Cluster
Let us follow one realistic connection from start to finish to see every idea working together.
An application team runs a consumer built with a client library from a few years ago. The platform team has just upgraded the cluster to a recent release. Nothing has been changed in the application. Will it still work? Let us watch.
Connection opens
The consumer connects to a bootstrap broker. Security setup, such as encryption and authentication, happens according to its configuration.
ApiVersions
The client sends the ApiVersions request at the newest version it knows. The recent broker knows that older version too, so it answers with its full table of supported ranges.
Overlap per API
For the metadata request, the client’s maximum is lower than the broker’s maximum, so it picks its own maximum. The same happens for fetch, group coordination and offset requests. In every case the highest shared version is the client’s own highest.
Metadata and group join
The client asks for metadata at its chosen version, discovers partition leaders and joins its consumer group using the versions negotiated earlier.
Fetching data
The client sends fetch requests at its older version. The broker serves the data. If the stored message format is newer than the client can read through that fetch version, the broker converts messages down to the older layout before sending.
Steady state
The consumer keeps working, unaware that the cluster is newer. It simply does not benefit from features that only exist in versions it cannot use.
sequenceDiagram
participant C as Older consumer
participant B as Recent broker
C->>B: ApiVersions
B-->>C: Supported ranges for every API
C->>C: Select own highest version per API
C->>B: Metadata at chosen version
B-->>C: Partition leaders
C->>B: JoinGroup and SyncGroup at chosen versions
B-->>C: Assignment
C->>B: Fetch at older version
B->>B: Down-convert if needed
B-->>C: Messages in older layout
What the team should conclude
The application works, but it is leaving benefits on the table and may be causing extra work for the brokers through down-conversion. That is a good reason to schedule an upgrade of the client library during normal maintenance, without any rush or outage. Compatibility gives you the time to do so calmly.
Being able to run an old client against a new cluster is a gift of time. Use that time to plan client upgrades rather than treating old clients as permanent residents.
17Errors and Failure Modes
Most version problems show up as a small family of recognizable symptoms.
Unsupported version error
The broker received a request at a version outside its supported range for that API. Usually caused by a custom client that skipped negotiation, a proxy that alters requests, or a client library with a bug in its version selection.
Connection closed right after connecting
A client using a very old or very unusual approach may send something the broker cannot parse at all, causing the broker to close the connection. Also appears when a non-Kafka client, such as a web browser or a health probe speaking another protocol, accidentally connects to the Kafka port.
Feature works on one cluster but not another
The highest shared version differs between the two clusters, so one cluster offers the version that carries the feature and the other does not.
Sudden failures after a major broker upgrade
Very old clients rely on versions that the new release has removed. Errors appear at connection or first request time.
Slow consumers after an upgrade
Old consumers may now trigger heavy down-conversion, raising CPU use on brokers and lowering throughput.
Corrupted or garbled requests
Hand-written protocol clients that mishandle sizes, headers or special cases such as the ApiVersions response header may produce requests the broker cannot make sense of.
| Symptom | First thing to check | Likely fix |
|---|---|---|
| Unsupported version messages | The versions the client sends versus what the broker lists | Upgrade or reconfigure the client, remove the interfering proxy |
| Immediate disconnects | What is actually connecting to the port | Fix the caller or network path |
| Missing feature | Highest shared version of the relevant request | Upgrade the side with the lower maximum |
| Failures after major upgrade | Client library ages versus the new minimum | Upgrade clients before or with the brokers |
| Higher broker CPU with old consumers | Down-conversion activity | Upgrade consumers, avoid old message formats |
If you implement a client yourself, always perform the ApiVersions exchange, respect the ranges and handle the fallback rule. Hard-coding a version that works today on your test cluster is a reliable way to break after the next upgrade.
18Security Considerations
Protocol versioning touches security in several quiet but important ways.
Authentication has its own versioned requests
Security mechanisms such as SASL use dedicated requests: one to choose a mechanism and one to carry the authentication exchange. These requests have versions, and the way the exchange is wrapped depends on the version. Older versions sent the authentication tokens in a raw form outside of normal request framing, while newer versions wrap them as ordinary requests and responses. Clients and brokers must agree on this, which is one more reason the ApiVersions exchange happens before authentication.
Very old versions may lack protections
Older request versions predate fixes and hardening. Keeping them enabled forever preserves their weaknesses.
Oversized or malformed requests
Every server that parses network input must guard against absurdly large sizes. Brokers enforce a maximum request size for this reason.
Unauthenticated exposure
Before authentication completes, only a small set of requests should be allowed, such as ApiVersions and the authentication requests themselves.
Information leaks through the handshake
Capability tables reveal software age. Exposing brokers to untrusted networks can help an attacker choose known weaknesses.
A building’s front desk will tell any visitor the opening hours, which is harmless, but will not open any inner doors until the visitor shows valid identification. The ApiVersions exchange is like asking about opening hours: allowed before identity checks, but only that and the identity checks themselves.
Practical protective measures
Encrypt all traffic
Use TLS so that requests, responses and any authentication material cannot be read or modified on the network.
Retire old client libraries
Use the version floor raised by newer releases as a chance to remove clients that rely on weaker or unmaintained protocol paths.
Restrict who can reach brokers
Use network policy so that only expected clients and cluster members can connect. Keep brokers off untrusted networks.
Limit request sizes
Keep the maximum request size at a value that fits your legitimate traffic, so accidental or hostile giant requests are refused early.
19Monitoring, Logging and Metrics
You can only manage version diversity if you can see it.
Questions worth answering with metrics
Which versions of each request are actually in use?
Broker request metrics can be broken down by request type and by version, showing how much traffic still uses old versions.
Which client libraries are connected?
Connection metrics grouped by client software name and version reveal the libraries in use across the organization.
Is down-conversion happening?
Broker metrics about message conversion show how much conversion work is being done and whether it is growing.
Are there version-related errors?
Error rates per request type, including unsupported-version errors, should normally be near zero.
Are brokers on the same release?
Inventory tooling and broker version information show whether a mixed-version window is open and how long it has lasted.
What is the finalized feature level?
The cluster’s finalized features should match what you intend after every upgrade.
| Signal | Where it appears | Healthy sign | Warning sign |
|---|---|---|---|
| Requests per second by request type and version | Broker request metrics | Traffic concentrated on recent versions | Large share on very old versions before a planned removal |
| Connections by client software | Broker connection metrics | Few distinct, recent library versions | Long tail of ancient libraries |
| Message conversion rate | Broker conversion metrics | Close to zero | Steady or rising conversion work |
| Unsupported version errors | Broker and client logs, error metrics | None | Any sustained occurrence |
| Finalized feature levels | Cluster feature tooling | Matches plan | Lagging behind after upgrade |
Plot the share of traffic by client library version over months. A healthy organization shows old versions shrinking over time. A flat long tail means nobody owns the upgrade work.
Logs that help
Broker logs record connection problems, unsupported requests and authentication failures. Client logs commonly print the versions negotiated when connecting, or provide a debug setting that does. When investigating, collect the client’s negotiated versions and the broker’s supported ranges side by side, and the mismatch usually becomes obvious.
20Inspecting Versions With Built-in Tools
Kafka ships tools that let you look at versions directly, with no guesswork.
Broker API versions tool
Connects to a broker and prints every request type it supports, with the minimum and maximum version of each. This is the quickest way to see what a cluster accepts.
Features tool
Shows supported and finalized feature levels for the cluster, including the metadata version, and can raise a level.
Protocol documentation
The official documentation lists every request type, its versions and the fields present in each version.
Network packet analyzers
General network analysis tools can decode Kafka traffic and show the API key, version and correlation identifier of each request on the wire.
Practical uses
Comparing two clusters
Run the broker API versions tool against each cluster and compare the maximum version of the request a feature depends on.
Confirming a rolling upgrade
After upgrading, ask each broker for its supported versions and confirm they match the new release before finalizing features.
Diagnosing a custom client
Capture traffic and examine the first few requests. If the client is skipping ApiVersions or sending a version outside the broker’s range, the capture will show it.
Add a version report to your upgrade checklist. Produce it before and after each change and store it with the change record, so future incidents can be compared against a known good state.
21Reading a Version Table Like an Expert
The official protocol documentation is full of tables. Learning to read them turns them from a wall of text into a quick answer machine.
Anatomy of an API entry
For every request type, the documentation shows the API key, the name, a description of what it does and the list of versions. Under each version you find the exact fields of the request and of the response, in order. Some fields appear only in later versions. Some are marked as optional, and some are described as nullable, meaning they may hold an explicit “nothing” value in certain versions.
Valid versions
The range of versions that exist for the request. The lower end may have been raised over time as very old versions were retired.
Flexible versions
The point from which a request uses compact encodings and tagged fields. Everything from that version onward uses the newer header.
Field version ranges
For each field, the versions in which it exists. A field with a range that starts at version five simply does not exist in versions zero through four.
Nullable versions
The versions in which a field may be explicitly empty. Outside those versions, an empty value is not allowed.
Default values
What a field means when it is absent in an older version, so that old and new senders behave consistently.
Error codes
The set of numeric codes a response may carry. Some codes are only returned by later versions.
Reading a train timetable takes practice. Once you know that a symbol means “weekends only” and another means “changes at the next station”, you can plan any trip in seconds. Protocol tables have their own symbols, and learning them once pays off for years.
A method for answering “can I use this feature”
Find the request
Identify which request type carries the feature, such as fetch for consumer isolation settings or produce for transactional identifiers.
Find the field
Locate the field that carries the feature and note the first version in which it appears.
Check the broker range
Use the broker API versions tool to find the broker’s maximum version of that request.
Check the client range
Check the client library’s documentation or release notes for the highest version it implements.
Compare
If the smaller of the two maximums is at or above the version that introduced the field, the feature is available. Otherwise, one side needs an upgrade.
The usable version for any request is the smaller of the two maximums, as long as it is not below both minimums. Remembering this one sentence lets you answer most compatibility questions without any tooling.
22Features That Depend on Specific Versions
Many familiar Kafka features exist only because a particular request grew a new version.
It is instructive to see how well-known capabilities map onto protocol evolution. The exact version numbers differ between requests and are best confirmed in the documentation, but the pattern is consistent: a capability arrives with a new request version or a new request type, and older versions simply cannot express it.
| Capability | What changed on the wire | Consequence for old peers |
|---|---|---|
| Message timestamps | A newer message format with a timestamp per record, carried by newer produce and fetch versions | Old peers cannot use time-based features |
| Record headers and batches | The second message format and produce and fetch versions that carry it | Old consumers need down-conversion to read modern data |
| Idempotent producers | Producer identity fields and sequence numbers in produce requests, plus a request to obtain a producer identity | Older brokers cannot deduplicate retries |
| Transactions | New request types for beginning, extending and completing transactions, plus isolation settings on fetch | Older brokers do not list these requests at all |
| Incremental fetch sessions | Fetch versions that let a consumer send only changes to its partition list | Old peers send the full list every time, costing bandwidth |
| Topic identifiers | Unique identifiers for topics carried by newer metadata and fetch versions | Old peers refer to topics by name only |
| Rack-aware reads | A field on fetch describing the client’s rack | Old clients cannot ask for nearby replicas |
| Client software reporting | Extra fields in a newer ApiVersions request | Old clients remain anonymous in broker metrics |
A restaurant may add a new section to its order form for allergies. Customers who use the old form can still order, but the kitchen cannot use allergy information from them. The new capability only works when the customer uses the new form.
What to notice in this list
New capabilities either extend a request or introduce a new one
Extending a request means old peers keep working with reduced features. Introducing a new request means old brokers simply do not advertise it, and clients must check for its presence before relying on it.
Capabilities are cumulative
A modern client on a modern broker uses many of these features together. On an older broker, the same client silently uses fewer of them.
Some features need both sides and the cluster
A few features additionally need a cluster-level feature flag to be finalized. Both request versions and the finalized level must allow the behavior.
When a client cannot use a feature because of a low shared version, it may quietly fall back to simpler behavior rather than failing loudly. A stronger guarantee that your application assumes, such as no duplicate messages, might therefore not be in force. Check the negotiated versions when correctness depends on a feature.
23Group Coordination and Its Own Protocol Versions
Consumer groups add a second layer of agreement: the members of a group must also agree with each other.
Two kinds of compatibility in one system
So far we have looked at client-to-broker compatibility. Consumer groups add another dimension. The members of a group must agree on how partitions are divided among them. In the classic design, the members themselves run the assignment logic, and they announce which assignment strategies they support when they join. Because the group may contain members running different library versions, a common strategy must be chosen that every member understands.
A group of friends splitting a restaurant bill must agree on a method: split equally, or each pay for what they ordered. Each friend states which methods they are happy with, and the group picks one everybody accepts.
The classic rebalance conversation
Find the coordinator
A member asks a broker which broker acts as the group coordinator for its group.
Join with supported strategies
Each member sends a join request listing the assignment strategies it supports, along with its subscription details.
Leader chooses
The coordinator picks one member as group leader and a strategy that all members support. The leader computes the assignment.
Sync the assignment
The leader sends the assignment to the coordinator using a sync request, and every member receives its own share.
Heartbeat
Members send regular heartbeats to prove they are alive. Missing heartbeats triggers a new rebalance.
The newer group protocol
Recent Kafka releases introduced a redesigned consumer group protocol in which the broker side takes over computing assignments, and members communicate through a dedicated heartbeat request. This reduces the disruption of rebalances and simplifies client logic. It is enabled through a combination of broker capability, cluster feature level and client configuration, so adopting it is a good example of how several kinds of versions must line up before a new behavior is used.
Classic protocol
- Widely supported by every client library of every age
- Assignment logic runs inside the clients
- Rebalances may pause all members briefly
- Custom assignment strategies are possible
Newer protocol
- Requires recent brokers and recent clients, plus the right settings
- Assignment is computed on the broker side
- Rebalances are more incremental and less disruptive
- Client libraries must add explicit support before it can be used
If one member of a group uses an old library, the whole group must fall back to behavior that member understands. Upgrading consumers as a whole group, rather than one at a time over months, avoids long periods stuck on the oldest common behavior.
24Client Libraries Across Languages
Kafka has no single official client for every language. Each library implements the protocol on its own schedule.
A diverse ecosystem
The Java client is maintained together with the Kafka broker and usually receives new protocol versions first. Libraries for other languages are built by other communities and companies. Some are thin wrappers around a shared native library, others are entirely separate implementations. Each decides how quickly to adopt new request versions and how much of the protocol to implement at all.
Native wrapper libraries
Several languages share one high-performance native implementation. Their protocol support tracks that shared library, so upgrading it upgrades many languages at once.
Pure-language implementations
Written entirely in one language for easy deployment. Their protocol coverage depends on how actively they are maintained.
Framework-embedded clients
Stream processing and connector frameworks bundle a specific client version. Their effective protocol support is that of the bundled version.
Protocol-aware proxies
Gateways that let simple clients talk to Kafka over other protocols. Their support depends on the gateway’s own version.
Version fallback settings
Because some old brokers could not answer the ApiVersions request, certain libraries historically offered settings such as a switch to disable the request and a fallback broker version to assume in its place. In today’s environment, where nearly all brokers support the request, these settings should normally be left alone, and depending on them is a sign of an outdated deployment.
Translators come in different kinds. Some are employed by the embassy and get every new vocabulary update immediately. Some are freelancers who learn new terms only when they have time. When accuracy matters, you check which kind of translator you have hired.
Choosing a library
Prefer libraries that are actively maintained, publish which protocol versions they implement, and document their compatibility with broker releases.
Understanding embedded versions
When a framework bundles a client library, upgrading the framework is how you upgrade the client. Read the framework’s release notes to learn the bundled version.
Language-specific behavior differences
Two libraries speaking the same versions may still expose different settings and defaults. Test behavior, not only connectivity.
A library you never chose directly may bring an old Kafka client along as a dependency. Inspect your dependency trees during audits so hidden old clients do not surprise you on upgrade day.
25Testing Compatibility Before It Bites
Compatibility that is never tested is only a hope. A little structure turns hope into evidence.
Build a compatibility matrix
List the client libraries and versions that matter in your organization on one axis and the broker releases you run or plan to run on the other. Each cell answers a question: does a representative application using this library work correctly against this broker release, including the features it relies on? Even a small matrix, filled in honestly, reveals risks that no amount of reading can.
| Client library | Current broker release | Next broker release | Notes to record |
|---|---|---|---|
| Oldest supported library | Tested, works | To be tested | Features lost, conversion activity |
| Most common library | Tested, works | To be tested | Performance and latency figures |
| Newest library | Tested, works | To be tested | New features available |
| Framework-bundled libraries | Tested, works | To be tested | Bundled version numbers |
What to test in each cell
Connect and authenticate
Confirm the handshake, negotiation and security exchange succeed cleanly.
Produce and consume
Send a realistic volume and verify that every message arrives, in order, with headers and timestamps intact.
Exercise the group protocol
Start, stop and restart members and confirm that rebalances complete and no partitions are orphaned.
Exercise special features
If you use transactions, idempotence, quotas or access control, test each feature specifically, and confirm the guarantees still hold.
Inject failures
Restart brokers one at a time during the test to imitate a rolling upgrade, and watch for errors or stalls.
Measure
Record throughput, latency and broker CPU, watching especially for jumps that suggest message conversion.
Before opening a new bridge to traffic, engineers drive test vehicles of every weight class across it. Testing every kind of client against every kind of broker is the same idea, done before real traffic depends on it.
Keep the compatibility tests in a pipeline that can be re-run against each new broker release candidate. What is automated actually gets run, and what gets run finds problems while they are still cheap to fix.
26The Rolling Upgrade Playbook
Everything in this tutorial exists so that upgrades can be done calmly, one piece at a time.
Read the upgrade notes
Study the release notes and upgrade guide for the target version. Look for removed protocol versions, changed defaults and any required intermediate releases.
Inventory clients
Use metrics to list client libraries and versions. Compare them with the minimum supported by the target release.
Upgrade stragglers first
If any clients are below the new floor, upgrade them before touching the brokers.
Rehearse in staging
Run the same upgrade on a staging cluster with representative clients and watch for errors and performance shifts.
Upgrade controllers, then brokers
Roll the new software through the controller quorum and then through the brokers, one node at a time, waiting for full health between steps.
Observe
Watch error rates, latency, conversion activity and client connectivity across a full business cycle.
Finalize new features
Only after stability is proven, raise the finalized feature level to unlock the new cluster behaviors.
Upgrade clients on their own schedule
Application teams move to newer client libraries at a comfortable pace, gaining access to newer request versions and features.
flowchart TB
A["Read upgrade notes"] --> B["Inventory client libraries"]
B --> C{"Any client below new floor?"}
C -->|"yes"| D["Upgrade those clients first"]
C -->|"no"| E["Rehearse in staging"]
D --> E
E --> F["Roll controllers then brokers"]
F --> G["Observe through a full cycle"]
G --> H["Finalize feature level"]
H --> I["Clients upgrade at their own pace"]
Renovating a busy shop while it stays open: you check the plan, move fragile items first, rehearse the trickiest part in a spare room, renovate one aisle at a time, watch that customers are still happy, and only then remove the temporary barriers.
Some major upgrades require passing through a specific intermediate release. Jumping past it can leave the cluster in a state that the target release does not know how to read. The upgrade guide states these requirements explicitly.
27Advantages and Trade-offs
Kafka’s versioning approach has made it easy to evolve, but it is not free of cost.
Advantages
- Rolling upgrades of brokers without stopping clients
- Clients and brokers can be upgraded in either order within supported ranges
- Each API evolves independently, so changes are small and focused
- Schemas generate code, reducing hand-written parsing errors
- Flexible versions make many future additions cheap
- Client software names and versions can be observed for planning
Disadvantages / Trade-offs
- A large matrix of API and version combinations to test and document
- Supporting old versions slows down development and can hide bugs
- Old clients can force costly down-conversion on brokers
- Behavior differences between versions can confuse troubleshooting
- Major releases eventually raise the floor, forcing client upgrades
- Custom client authors must implement negotiation correctly
Comparison with other approaches
| Approach | How it works | Strength | Weakness |
|---|---|---|---|
| One global protocol version | All messages share a single version number | Simple to reason about | Any change forces everyone to move; no partial upgrades |
| Per-request versions with negotiation, as in Kafka | Each request type has its own range, chosen per connection | Fine-grained evolution and mixed-age fleets | Larger testing matrix |
| Self-describing text formats | Messages carry field names, receivers ignore unknown ones | Very tolerant of change | Larger and slower to parse |
| Generated binary schemas with optional fields | Fields are numbered, unknown ones skipped | Compact and tolerant | Requires discipline in field numbering and defaults |
28Design Patterns and Best Practices
These habits keep upgrades boring, which is exactly what you want.
Use official client libraries
Well-maintained libraries implement negotiation, fallbacks and special cases correctly. Writing your own client is rarely worth the risk.
Keep client libraries reasonably current
Schedule regular library upgrades so no application drifts far behind the floor of supported versions.
Track client versions centrally
Feed the client software metrics into a dashboard owned by the platform team, with a list of the applications behind each library version.
Announce a deprecation calendar
Tell application teams months ahead which client versions will stop working, and when.
Separate software rollout from feature finalization
Finish and validate the software upgrade first. Finalize new features later, as a separate reversible-in-spirit step.
Test with the oldest client you support
Keep a small test application built with your oldest supported library and run it against every candidate cluster release.
Record a version report at each change
Store the API version ranges and feature levels before and after every upgrade for later comparison.
Avoid version-specific hacks in applications
Applications should not depend on undocumented behavior of one version. Rely on documented, supported behavior.
Client version drift is a shared problem that nobody owns unless someone is assigned. Make it explicit that the platform team measures it and application teams act on it.
29Anti-patterns to Avoid
Each of these looks harmless until an upgrade day arrives.
Problem
Leaving client libraries untouched for years because “they still work”.
Why It’s Harmful
The gap to the supported floor grows silently. When a major broker upgrade raises the minimum, dozens of applications may break at once, with the upgrade blamed for problems that were building for years.
Correct Approach
Upgrade client libraries on a regular cadence, and monitor the age distribution of client versions.
Problem
Hard-coding protocol versions in a custom client or proxy.
Why It’s Harmful
The code works against today’s brokers and fails as soon as ranges change. It also prevents automatic use of newer, better versions.
Correct Approach
Always perform the ApiVersions exchange and pick the highest shared version for each request type.
Problem
Finalizing new cluster features in the same step as the software upgrade.
Why It’s Harmful
Once features are finalized, rolling the software back may no longer be safe. You lose your safety window at the moment when unexpected problems are most likely to appear.
Correct Approach
Upgrade the software, observe for a full cycle, and then finalize features as a separate, deliberate change.
Problem
Ignoring down-conversion cost.
Why It’s Harmful
Old consumers quietly force brokers to convert message formats and lose zero-copy transfer, raising CPU use and lowering throughput. The cost is spread across the cluster and hard to attribute.
Correct Approach
Monitor conversion metrics and upgrade the consumers responsible.
Problem
Putting network devices or proxies that rewrite or inspect Kafka traffic without understanding versions.
Why It’s Harmful
A device that does not understand newer versions may drop, truncate or corrupt requests, producing baffling intermittent failures.
Correct Approach
Use transport-level tools that treat Kafka traffic as opaque bytes, or use proxies that explicitly track Kafka protocol versions.
Problem
Skipping the release notes because “it is only a minor bump”.
Why It’s Harmful
Deprecations, changed defaults and behavior differences are announced in the notes. Missing them turns a routine change into an incident.
Correct Approach
Read the notes for every release between your current and target version, and record the items that affect you.
30Real-World and Industry Perspectives
Protocol versioning shows up in daily practice across the Kafka ecosystem.
Managed cloud Kafka services
Cloud providers regularly upgrade brokers behind the scenes. Their customers can keep using existing client libraries because of the compatibility rules, and providers publish the minimum supported client versions well in advance.
Multi-language client ecosystem
Libraries in many programming languages implement the same protocol independently. Each must negotiate versions and handle special cases. This is why interoperability testing and the public protocol documentation matter so much.
Replication between clusters
Tools that mirror data between clusters often connect to clusters of different ages, sometimes from different providers. Negotiation lets one tool serve both, using the best version each cluster supports.
Data platforms and connectors
Connector frameworks embed client libraries. Upgrading the framework upgrades every connector at once, which is why platform teams track the embedded library version carefully.
Protocol-aware proxies and gateways
Some products sit between clients and brokers to add features such as routing, quotas or multi-tenancy. To work correctly they must understand the protocol versions and keep up with new ones, which is a significant engineering commitment.
Regulated environments
Organizations under strict controls often insist on documented upgrade plans. The predictable versioning policy makes it possible to write such plans with confidence.
31Common Myths About Kafka Protocol Versions
A few persistent misunderstandings cause a surprising number of upgrade mistakes.
Problem
“The client and broker must run the same Kafka version.”
Why It’s Harmful
Believing this makes teams postpone upgrades in fear or, worse, force risky simultaneous upgrades of everything at once.
Correct Approach
Clients and brokers only need overlapping version ranges for the requests they use. Different releases can and do work together within supported limits.
Problem
“If a connection works, every feature works.”
Why It’s Harmful
A connection can succeed using low shared versions while silently lacking newer capabilities, leading to surprises when an application assumes a guarantee that is not in force.
Correct Approach
Verify that the negotiated versions include the ones that carry the features your application depends on.
Problem
“Old clients will work with newer brokers forever.”
Why It’s Harmful
Compatibility is generous but not endless. Major releases raise the floor, and a client ignored for years may suddenly be refused.
Correct Approach
Treat backward compatibility as a grace period and keep client libraries on a regular upgrade schedule.
Problem
“Protocol versions and message format versions are the same thing.”
Why It’s Harmful
Confusing the two leads to wrong diagnoses, such as blaming a request version for a storage layout issue, and to mistaken assumptions about which upgrade will fix a problem.
Correct Approach
Remember the layers: request versions shape the conversation, message formats shape stored records, and feature levels describe cluster-wide agreement.
When something behaves oddly, ask three questions instead of trusting a myth: which request is involved, what is the highest version both sides share for it, and does that version carry the behavior I expect?
32Frequently Asked Questions
No. Every request type has its own version numbering. A single connection can carry requests of many types at many different versions.
The client chooses. The broker advertises the range it supports for each request type, and the client picks the highest version that both sides understand.
Because it is the first exchange on a connection, before anything is known about the peer. Using the simplest header lets even very old clients read at least the essential part, including an error and the supported range.
The broker replies in the oldest response format with an unsupported-version error and includes its supported table, so the client can retry with a version the broker understands.
The request version describes the layout of the request and response bodies. The message format version describes the layout of the records inside a batch. They evolve separately.
It is the broker converting stored messages into an older layout for an old consumer. It costs CPU and memory and stops the broker from using its most efficient data path, so many old consumers can noticeably reduce broker performance.
They are optional fields at the end of a flexible-version message, each with a tag number and a size. A receiver that does not recognize a tag can skip it safely, which makes many changes possible without a new version.
Usually yes, as long as the old versions it uses are still supported. Major releases can remove very old versions, in which case very old clients must be upgraded first.
Generally yes within a broad range, using the highest versions the old broker supports. Features that need newer request versions will not be available.
A request version range describes what a piece of software can read. A finalized feature level describes what the whole cluster has agreed to use. Operators raise the latter deliberately, after all members are upgraded.
Recent brokers record the client software name and version reported during the handshake and expose it in metrics and logs. Use that to build an inventory.
Because the highest shared version of the relevant request may differ. One cluster may offer the version that carries the feature and the other may not.
Rarely. The protocol has many special cases, such as the header rule for ApiVersions and fallback behavior, and official libraries have already solved them. If you must, follow the public protocol documentation closely and test against many broker versions.
Not directly. It is a cluster-level decision about internal behavior. Clients continue to negotiate request versions with brokers as before, though new cluster capabilities may later show up as newer version ranges or features.
Measure client library versions continuously and upgrade them steadily, so no application is ever far behind the versions your brokers support.
No. The broker only publishes what it accepts. Every decision about which version to send is taken inside the client library, which is why a buggy or outdated library is a common source of version errors.
Kafka reuses one well-tested request for both jobs, adding a few identifying details for replicas. This means replication between brokers of different ages relies on the same negotiation rules that clients use, which keeps rolling upgrades of the cluster safe.
Yes. Every request header carries the version, so packet captures show it directly, and broker request metrics can be grouped by request type and version. Client debug logging often prints the negotiated versions at connection time.
A transparent network proxy that only forwards bytes is fine. A proxy that understands Kafka requests must itself support the versions in use, otherwise it may reject or damage newer requests. Always check the proxy’s supported range.
Generally newer versions carry more capability and better efficiency, but they are not automatically better for every application. The right approach is to let the client choose the highest shared version, and to test that the resulting behavior matches your expectations.
Because each version is frozen once released, even a tiny change becomes a new version. The many small steps are a deliberate price paid for never altering the meaning of something that peers already depend on.
The release notes and the upgrade guide for every release between your current and target version, with special attention to removed protocol versions, changed defaults and any required intermediate release.
33Summary and Key Takeaways
Kafka’s protocol versioning is a quiet piece of engineering that allows an enormous, mixed-age fleet of clients and brokers to work together. Every request type carries its own version, every broker publishes the ranges it accepts, and every client chooses the highest version both sides understand, starting with a special handshake that even the oldest software can begin. Flexible encodings, careful compatibility rules, generated code, feature flags and a disciplined deprecation process complete the picture. Understanding these pieces lets you plan upgrades with confidence, diagnose odd compatibility errors quickly and keep your platform healthy for years.
Key Takeaways
- Versions are per request type — each API key has its own version range, and a version number is meaningful only together with its API key.
- The client negotiates — brokers advertise minimum and maximum versions through the ApiVersions request, and the client picks the highest shared version for every API.
- The handshake has a safety net — the ApiVersions response uses the oldest header so that even an unsupported request can be answered in a form the client can read and retry.
- Flexible versions make change cheaper — tagged fields and compact encodings let many additions ride along without breaking older readers.
- Three layers of versioning exist — request versions, message format versions and cluster feature levels each answer a different question and evolve separately.
- Compatibility is broad but bounded — old and new clients and brokers interoperate within supported ranges, and major releases eventually raise the floor.
- Measure and plan — track client library versions and down-conversion, upgrade stragglers first, and finalize new features only after the software rollout has proven stable.