skip to content

How does Kafka's wire-protocol API versioning let clients of different versions and languages interoperate with brokers, and what is the role of ApiVersions negotiation?

level: principalimportance: should knowfreq 35%

answer

  1. API key + per-key version, independent
  2. ApiVersions request (KIP-35) at connect
  3. broker returns [min,max] per API key
  4. client picks highest mutual version
  5. brokers-first upgrade; IBP + flexible fields

basics

~20 s

Each Kafka request type (Produce, Fetch, Metadata, etc.) has its own API key and an independently incrementing version number. On connect, a client sends an ApiVersions request; the broker replies with the min/max version it supports per API key. The client then picks the highest version both sides support. This per-API negotiation lets old/new and JVM/non-JVM clients interoperate with brokers across versions.

solid answer

~50 s

Kafka's protocol is a set of request/response types, each identified by an API key (Produce=0, Fetch=1, Metadata=3, ApiVersions=18, etc.) and versioned independently. When a connection opens, the client issues an ApiVersions request (KIP-35); the broker returns, per API key, the [min_version, max_version] it supports. The client intersects those ranges with its own supported versions and uses the highest mutually supported version for each request. This is why a newer client can talk to an older broker (and vice versa) and why librdkafka and the JVM client interoperate despite separate codebases — they negotiate against the broker's advertised capabilities rather than assuming a global version. Newer protocol versions add fields, change encodings (e.g. flexible/tagged fields via KIP-482), or introduce features; flexible versions allow forward-compatible optional fields. Operationally this underpins safe rolling upgrades: bump brokers first (they support old request versions), then clients. The broker's overall compatibility is bounded by inter.broker.protocol.version and message format settings.

go deeper

for a junior

Know that clients and brokers negotiate which protocol version to use so different versions can talk.

for a middle

Explain the ApiVersions handshake and that the client picks the highest version both support per request type.

for a senior

Detail per-API-key versioning, flexible fields, and how negotiation enables cross-version/cross-language interop and rolling upgrades.

for a principal

Plan cluster upgrades end to end: broker-first ordering, inter.broker.protocol.version staging, down-conversion costs, feature gating by negotiated version, and diagnosing UNSUPPORTED_VERSION at scale.

**Why versioning exists.** Kafka clients and brokers are upgraded independently, on different schedules, in many languages. Without a negotiation mechanism, any version mismatch would break communication. Kafka solves this with **fine-grained, per-request-type protocol versioning** plus a runtime negotiation handshake. **The protocol shape.** Kafka's wire protocol is a catalog of **request/response message types**, each with: - an **API key** — a stable integer identifying the request type. Examples: `Produce=0`, `Fetch=1`, `ListOffsets=2`, `Metadata=3`, `OffsetCommit=8`, `OffsetFetch=9`, `FindCoordinator=10`, `JoinGroup=11`, `ApiVersions=18`, and so on. - an **API version** — a small integer that **increments independently per API key** whenever that request/response's schema changes (a new field, a changed encoding, a new behavior). So 'Fetch v12' and 'Produce v9' are independent; bumping one never bumps the other. **The ApiVersions handshake (KIP-35).** When a client establishes a connection (after SASL/TLS), it sends an **ApiVersions request** (API key 18). The broker replies with, **for every API key it supports**, a `[min_version, max_version]` pair. The client then, for each request type it wants to use, computes the **intersection** of the broker's supported range and its own supported range and selects the **highest mutually supported version**. If there's no overlap for a required API, the client errors out (UNSUPPORTED_VERSION). This is the linchpin of interoperability: - A **newer client** can talk to an **older broker** by downgrading individual requests to versions the broker understands. - An **older client** works with a **newer broker** because brokers retain support for old request versions. - **librdkafka and the JVM client** interoperate with the same brokers despite being separate implementations, because each independently negotiates against the broker's advertised capabilities rather than hardcoding a global protocol version. **Evolving the encoding: flexible/tagged fields (KIP-482).** Originally, adding a field meant a hard version bump and rigid parsing. **Flexible versions** introduced **optional tagged fields** and compact (varint-length) encodings, so brokers/clients can add optional fields that older peers simply skip — improving forward compatibility and reducing version churn. A given API key has a 'flexible version' threshold above which the flexible encoding applies. **Operational consequences (the principal-level payoff):** - **Rolling upgrades:** the supported pattern is **upgrade brokers first**, then clients. Brokers on a newer version still accept old request versions, so existing clients keep working during the broker roll; afterwards clients can be upgraded to use newer request versions/features. - **inter.broker.protocol.version (IBP):** controls the protocol version brokers use to talk to **each other**; you typically only advance it after all brokers are upgraded, then perform a second rolling restart. This gates when cluster-internal new features 'switch on.' - **Message format / down-conversion:** if clients request an older fetch version than the on-disk message format, the broker may **down-convert** messages (CPU/heap cost, loses zero-copy). Keeping clients and `log.message.format` aligned avoids this tax. - **Capability gating:** a client only uses a feature if the negotiated version supports it — e.g. idempotent/transactional produce, incremental fetch sessions (KIP-227), or the new consumer group protocol (KIP-848) — which ties back to JVM-vs-librdkafka parity: a feature requires both the right client AND a broker advertising the needed API version. **Diagnostics.** The `kafka-broker-api-versions.sh` CLI dumps each broker's supported `[min,max]` per API key — invaluable when debugging UNSUPPORTED_VERSION errors or planning an upgrade. UNSUPPORTED_VERSION almost always means a client is asking for a request version the broker (or a feature flag) doesn't support yet. **Bottom line.** Per-API independent versioning + the ApiVersions handshake + flexible fields are what make Kafka's enormous, multi-language, multi-version client ecosystem hang together. Clients negotiate capabilities at connect time instead of assuming a monolithic version, which is precisely why cross-version and cross-language interoperability is robust.

  • In what order do you roll a Kafka cluster + clients upgrade, and why?
    Brokers first, then clients. Newer brokers still accept old request versions (per ApiVersions negotiation), so existing clients keep working during the broker roll. You advance inter.broker.protocol.version only after all brokers are upgraded (a second restart), then upgrade clients to use newer request versions/features.
  • What does an UNSUPPORTED_VERSION error usually indicate?
    The client tried to use a request version (or a feature requiring one) that the broker doesn't support — often a too-new client against an old broker, or a feature gated behind an unadvanced inter.broker.protocol.version. Check kafka-broker-api-versions.sh and align versions.
  • What problem did KIP-482 flexible/tagged fields solve?
    It allowed adding optional fields to protocol messages without a hard, breaking version bump: tagged fields are skippable by peers that don't understand them, and compact varint encodings reduce overhead — improving forward compatibility and reducing version churn.

saying these in an interview costs you the question

  • Saying Kafka has a single global protocol version — versions are per API key and negotiated independently.
  • Claiming clients and brokers must be the exact same version — negotiation is precisely what avoids that.
  • Recommending upgrading clients before brokers — the supported order is brokers first.
  • Ignoring down-conversion cost when clients lag the on-disk message format.
  • Confusing inter.broker.protocol.version (broker-to-broker) with client request versions.

context