# How it works
The library talks Kafka's network protocol itself. There is no C Kafka library in the process.
## Producer
1. You call `try_send` (throughput), `send_all` (many offsets), or `send`
(one offset future per record).
2. The record is given a partition **before** it is queued (the
[`Partitioner`](../src/partitioner.rs): murmur2 if there is a key,
round-robin if not, or `ProducerConfig::partitioner`). Until metadata for
that topic is cached, `try_send` returns `QueueFull` and `send` /
`send_all` wait.
3. The record goes onto the queue for **one** TCP connection: `partition % connections`. Idempotent sequences for a partition never share a socket with another worker.
4. That connection's worker waits a few milliseconds (`linger`) or until the batch is big enough, then writes a Produce request.
5. Several Produce requests can be in flight on the same socket.
6. `flush` waits for those responses and returns the first broker error. `try_send` Ok only means queued.
`ProducerConfig`, `ConsumerConfig`, and `AdminConfig` accept chainable
builders (`acks`, `sasl`, `tls`, `isolation`, `delivery_timeout`, `max_block`,
`buffer_memory`, `max_request_size`, `retry_backoff`, `reconnect_backoff`, `connections_max_idle`, `transaction_timeout`, `metadata_max_age`, …). The raw fields remain writable.
`ConsumerConfig.isolation_level` is [`IsolationLevel`](../src/config.rs)
(not a raw `i8`). `ConfigResourceType` and `ScramMechanism` type admin
config-resource and user-SCRAM calls.
The hot path copies each payload once into the Kafka record batch and checksums it with CRC32-C.
## Consumer
`Consumer` is manual: you say topic, partition, offset, then `fetch`.
`fetch` / group `poll` return [`ConsumerRecords`](../src/consumer.rs)
(Java `count` / `partitions` / `records` / `nextOffsets`). Share `poll` returns
[`ShareRecords`](../src/share.rs).
`fetch` sends one request per partition leader and waits for all of them
when there is more than one. `seek_to_beginning` / `seek_to_end` call
ListOffsets for every assigned partition; `seek_to_beginning_of` /
`seek_to_end_of` take a partition list (Java `seekToBeginning` /
`seekToEnd`). `pause` / `resume` skip
assigned partitions without dropping them; pause survives group rebalance.
`position` is the next fetch offset (`position_of` takes `TopicPartition`).
`seek_with_metadata` is Java `seek(TopicPartition, OffsetAndMetadata)`:
the offset is the next fetch position and the leader epoch is Fetch
`LastFetchedEpoch` (KIP-320). `seek` / `seek_to` still clear the epoch.
`partitions_for` / `beginning_offsets` / `end_offsets` wrap Metadata and
ListOffsets and take `TopicPartition`. Each has a `_timeout` variant
(Java `partitionsFor(String, Duration)` / `beginningOffsets` /
`endOffsets` / `listTopics(Duration)` / `offsetsForTimes(Map, Duration)`).
`partitions_for` includes leader epoch
and offline replicas (Java `offlineReplicas`). `list_offset` is ListOffsets for one
partition. `Admin::list_offsets` is Java `Admin.listOffsets` (earliest / latest /
timestamp / `OffsetSpec`; one ListOffsets RPC per partition leader; returns
`OffsetAndTimestamp`). `Admin::list_offsets_with_isolation` is Java
`listOffsets` plus `ListOffsetsOptions.isolationLevel`.
`Admin::list_offsets_timeout` / `list_offsets_with_isolation_timeout` are Java
`ListOffsetsOptions.timeoutMs` (RPC deadline and ListOffsets v10 TimeoutMs).
`Admin::fence_producers` is Java
`Admin.fenceProducers` (InitProducerId on the transaction coordinator).
`Admin::fence_producers_timeout` is Java `FenceProducersOptions.timeoutMs`
(RPC deadline and `transaction.timeout.ms`).
`Admin::force_terminate_transaction` is Java `forceTerminateTransaction`
(same InitProducerId fence for one `transactional.id`).
`Admin::force_terminate_transaction_timeout` is the same plus timeout.
`Admin::abort_transaction` is Java `abortTransaction` (WriteTxnMarkers
ABORT on the partition leader).
`Admin::abort_transaction_timeout` is Java `AbortTransactionOptions.timeoutMs`
(RPC deadline; WriteTxnMarkers has no TimeoutMs; caps `NOT_LEADER_OR_FOLLOWER`).
`Admin::describe_transactions_timeout` is Java
`DescribeTransactionsOptions.timeoutMs` (RPC deadline; DescribeTransactions
has no TimeoutMs; caps coordinator retries).
`Admin::list_transactions_timeout` / `Admin::list_transactions_with_duration_timeout`
are Java `ListTransactionsOptions.timeoutMs` (RPC deadline; ListTransactions
has no TimeoutMs; `with_duration` is `filterOnDuration`, not TimeoutMs).
`Admin::list_transactions_all` is Java `listTransactions()`.
`Admin::describe_producers_for` is Java `describeProducers(Collection)`
(one DescribeProducers RPC per partition leader; Topics of N).
`Admin::describe_producers_for_on_broker` is Java
`DescribeProducersOptions.brokerId` (one RPC to that broker; `NOT_LEADER_OR_FOLLOWER`
is not retried on the Metadata leader).
`Admin::describe_producers_timeout` / `Admin::describe_producers_for_timeout`
are Java `DescribeProducersOptions.timeoutMs` (RPC deadline;
DescribeProducers has no TimeoutMs).
`Admin::remove_members_from_consumer_group` is Java
`removeMembersFromConsumerGroup` (LeaveGroup v3–v5 by `group.instance.id`;
v5 sends `DEFAULT_LEAVE_GROUP_REASON`).
`Admin::remove_all_members_from_consumer_group` is Java
`RemoveMembersFromConsumerGroupOptions.removeAll` (DescribeGroups then LeaveGroup).
`Admin::remove_members_from_consumer_group_with_reason` /
`Admin::remove_all_members_from_consumer_group_with_reason` are Java
`RemoveMembersFromConsumerGroupOptions.reason` (LeaveGroup v5; empty uses
`DEFAULT_LEAVE_GROUP_REASON`; truncated to 255 characters). Kafka 4.0
`KafkaAdminClient` does not wire this field; later Java does.
`Admin::remove_members_from_consumer_group_timeout` /
`Admin::remove_all_members_from_consumer_group_timeout` are Java
`RemoveMembersFromConsumerGroupOptions.timeoutMs` (RPC deadline; LeaveGroup
and DescribeGroups have no TimeoutMs; caps coordinator retries).
`Admin::describe_features` is Java `describeFeatures` (ApiVersions v3–v4
tagged fields; v4 SupportedFeatures.MinVersion 0, KAFKA-17011;
[`FeatureMetadata`](../src/admin.rs)).
`Admin::describe_features_timeout` is Java `DescribeFeaturesOptions.timeoutMs`
(RPC deadline; ApiVersions has no TimeoutMs).
`Admin::update_features_timeout` / `Admin::update_features_with_timeout` are
Java `UpdateFeaturesOptions.timeoutMs` (RPC deadline and TimeoutMs).
`Admin::list_topics` / `Admin::list_topics_with` / `Admin::list_topics_timeout` / `Admin::describe_topics` / `Admin::describe_topics_with` / `Admin::describe_topics_timeout` / `Admin::describe_topics_with_partition_limit` / `Admin::describe_topics_by_id` are Java `listTopics` / `ListTopicsOptions.listInternal` / `ListTopicsOptions.timeoutMs` /
`describeTopics` (DescribeTopicPartitions api 75, KIP-966; Metadata fallback when api 75 is missing) / `DescribeTopicsOptions.includeAuthorizedOperations` / `DescribeTopicsOptions.timeoutMs` / `DescribeTopicsOptions.partitionSizeLimitPerResponse` / `describeTopics(TopicCollection.ofTopicIds)` (Metadata v10+).
`Admin::describe_topic_partitions_timeout` is the crate-first RPC deadline
(DescribeTopicPartitions has no TimeoutMs). Java `describeTopics` is
`Admin::describe_topics_timeout`. `Admin::describe_acls_with` is Java
`describeAcls(AclBindingFilter)`. `Admin::describe_acls_any` is Java
`describeAcls(AclBindingFilter.ANY)`. `Admin::delete_acls_with` is Java
`deleteAcls(Collection)` (DeleteAcls Filters of N). `Admin::describe_classic_groups` is Java
`describeClassicGroups` (DescribeGroups v0–v6; FindCoordinator v4+ CoordinatorKeys of N). `Admin::describe_consumer_groups` is Java
`describeConsumerGroups` (ConsumerGroupDescribe v0–v1 first, then DescribeGroups v0–v6 on per-group `UNSUPPORTED_VERSION` / `GROUP_ID_NOT_FOUND` or when api 69 is not advertised; FindCoordinator v4+ CoordinatorKeys of N).
`Admin::describe_classic_groups_timeout` / `Admin::describe_consumer_groups_timeout` /
`Admin::describe_groups_timeout` are Java `DescribeClassicGroupsOptions` /
`DescribeConsumerGroupsOptions.timeoutMs` (RPC deadline; DescribeGroups has no
TimeoutMs). `Admin::consumer_group_describe` is
ConsumerGroupDescribe v0–v1 (flexible from v0; v1 MemberType; FindCoordinator v4+ CoordinatorKeys of N).
`Admin::consumer_group_describe_timeout` is the crate-first RPC deadline
(ConsumerGroupDescribe has no TimeoutMs). Java `describeConsumerGroups` is
`Admin::describe_consumer_groups_timeout` (api 69 first, then DescribeGroups). `Admin::list_consumer_groups` is Java
`listConsumerGroups` (ListGroups v0–v5). `Admin::list_groups_all` / `Admin::list_consumer_groups_all` are Java
`listGroups()` / `listConsumerGroups()`. `Admin::list_groups_timeout` /
`Admin::list_consumer_groups_timeout` are Java `ListGroupsOptions` /
`ListConsumerGroupsOptions.timeoutMs` (RPC deadline; ListGroups has no
TimeoutMs). `Admin::delete_consumer_groups` is Java
`deleteConsumerGroups` (DeleteGroups v0–v2; classic through v1, flexible v2,
throttle v0+; FindCoordinator v4+ CoordinatorKeys of N).
`Admin::delete_groups_timeout` / `Admin::delete_consumer_groups_timeout` /
`Admin::delete_share_groups_timeout` are Java `DeleteConsumerGroupsOptions` /
`DeleteShareGroupsOptions.timeoutMs` (RPC deadline; DeleteGroups has no
TimeoutMs). `Admin::describe_share_groups` is Java
`describeShareGroups` (ShareGroupDescribe v0–v1; FindCoordinator v4+ CoordinatorKeys of N).
`Admin::share_group_describe_timeout` / `Admin::describe_share_groups_timeout`
are Java `DescribeShareGroupsOptions.timeoutMs` (RPC deadline;
ShareGroupDescribe has no TimeoutMs). `Admin::list_client_metrics_resources` is Java
`listClientMetricsResources` (ListConfigResources v0–v1 CLIENT_METRICS).
`Admin::list_config_resources_all` is Java `listConfigResources()`.
`Admin::list_config_resources_timeout` /
`Admin::list_client_metrics_resources_timeout` are Java
`ListConfigResourcesOptions` / `ListClientMetricsResourcesOptions.timeoutMs`
(RPC deadline; ListConfigResources has no TimeoutMs). `Admin::list_share_group_offsets` is Java
`listShareGroupOffsets` (DescribeShareGroupOffsets; FindCoordinator v4+ CoordinatorKeys of N).
`Admin::describe_share_group_offsets_timeout` / `Admin::list_share_group_offsets_timeout`
are Java `ListShareGroupOffsetsOptions.timeoutMs` (RPC deadline;
DescribeShareGroupOffsets has no TimeoutMs).
`Admin::alter_share_group_offsets_timeout` / `Admin::delete_share_group_offsets_timeout`
are Java `AlterShareGroupOffsetsOptions` / `DeleteShareGroupOffsetsOptions.timeoutMs`
(RPC deadline; these RPCs have no TimeoutMs). `Admin::delete_consumer_group_offsets` is Java
`deleteConsumerGroupOffsets` (OffsetDelete).
`Admin::delete_offsets_timeout` / `Admin::delete_consumer_group_offsets_timeout`
are Java `DeleteConsumerGroupOffsetsOptions.timeoutMs` (RPC deadline;
OffsetDelete has no TimeoutMs).
`Admin::alter_consumer_group_offsets_timeout` is Java
`AlterConsumerGroupOffsetsOptions.timeoutMs` (RPC deadline; OffsetCommit has
no TimeoutMs). `Admin::delete_share_groups` is Java
`deleteShareGroups` (DeleteGroups v0–v2). `Admin::describe_client_quotas` /
`Admin::alter_client_quotas` are Java `describeClientQuotas` /
`alterClientQuotas` (v0–v1; classic v0, flexible v1).
`Admin::describe_client_quotas_timeout` / `Admin::alter_client_quotas_timeout`
are Java `DescribeClientQuotasOptions` / `AlterClientQuotasOptions.timeoutMs`
(RPC deadline; these RPCs have no TimeoutMs; alter also caps `NOT_CONTROLLER`).
`Admin::describe_client_quotas_all` is Java `describeClientQuotas(ClientQuotaFilter.all())`.
`Admin::describe_client_quotas_with` is Java `describeClientQuotas(ClientQuotaFilter)` (`contains` / `containsOnly`).
`Admin::alter_user_scram_credentials_with` is Java `alterUserScramCredentials(List)` (`UserScramCredentialAlteration`).
`Admin::alter_user_scram_credentials_timeout` /
`Admin::describe_user_scram_credentials_timeout` are Java
`AlterUserScramCredentialsOptions` / `DescribeUserScramCredentialsOptions.timeoutMs`
(RPC deadline; these RPCs have no TimeoutMs; both cap `NOT_CONTROLLER`).
`Admin::describe_user_scram_credentials_all` is Java `describeUserScramCredentials()`.
`Admin::unregister_broker_timeout` is Java
`UnregisterBrokerOptions.timeoutMs` (RPC deadline; UnregisterBroker has no
TimeoutMs; caps `NOT_CONTROLLER`).
`Admin::allocate_producer_ids_timeout` is the crate-first RPC deadline
(AllocateProducerIds has no TimeoutMs; caps `NOT_CONTROLLER`). Java `Admin`
has no `allocateProducerIds`. `Admin::new` does not require AllocateProducerIds,
UnregisterBroker, DescribeProducers, DescribeCluster, UpdateFeatures,
DescribeClientQuotas, AlterClientQuotas, AlterUserScramCredentials,
DescribeUserScramCredentials, AlterReplicaLogDirs, DescribeLogDirs, the
delegation-token APIs, DescribeTransactions, ListTransactions,
AlterPartitionReassignments, ListPartitionReassignments, OffsetDelete, IncrementalAlterConfigs, ShareGroupDescribe,
the share-offset RPCs, ListConfigResources, GetTelemetrySubscriptions, PushTelemetry, or AssignReplicasToDirs;
those methods
return `Error::Unsupported` when the broker omits them.
`Admin::assign_replicas_to_dirs_timeout` is Java
`AssignReplicasToDirsOptions.timeoutMs` (RPC deadline; AssignReplicasToDirs has no
TimeoutMs; caps `NOT_CONTROLLER`).
`Admin::alter_replica_log_dirs` is Java
`alterReplicaLogDirs` (v1–v2; classic v1, flexible v2).
`Admin::alter_replica_log_dirs_timeout` is Java
`AlterReplicaLogDirsOptions.timeoutMs` (RPC deadline; AlterReplicaLogDirs has no TimeoutMs). `Admin::create_delegation_token` is Java
`createDelegationToken` (v1–v3; classic v1, flexible v2, owner/requester v3). `Admin::create_delegation_token_default` is Java
`createDelegationToken()`. `Admin::renew_delegation_token` is Java
`renewDelegationToken` (v1–v2; classic v1, flexible v2). `Admin::renew_delegation_token_hmac` is Java
`renewDelegationToken(byte[])`. `Admin::expire_delegation_token` is Java
`expireDelegationToken` (v1–v2; classic v1, flexible v2). `Admin::expire_delegation_token_hmac` is Java
`expireDelegationToken(byte[])`. `Admin::describe_delegation_token` is Java
`describeDelegationToken` (v1–v3; classic v1, flexible v2, TokenRequester v3).
`Admin::describe_delegation_tokens` is Java `describeDelegationToken()`.
`Admin::create_delegation_token_timeout` / `Admin::renew_delegation_token_timeout` /
`Admin::expire_delegation_token_timeout` / `Admin::describe_delegation_token_timeout`
are Java `CreateDelegationTokenOptions` / `RenewDelegationTokenOptions` /
`ExpireDelegationTokenOptions` / `DescribeDelegationTokenOptions.timeoutMs`
(RPC deadline; these RPCs have no TimeoutMs). `Admin::describe_replica_log_dirs` is Java
`describeReplicaLogDirs`. `Admin::describe_broker_log_dirs` is Java
`describeLogDirs(Collection<Integer>)` (null-topics DescribeLogDirs v1–v4 on
each broker). `Admin::describe_log_dirs_timeout` /
`Admin::describe_replica_log_dirs_timeout` /
`Admin::describe_broker_log_dirs_timeout` are Java
`DescribeLogDirsOptions.timeoutMs` (RPC deadline; DescribeLogDirs has no
TimeoutMs). `Admin::metrics` is Java `Admin.metrics()` (`AdminMetrics`
snapshot; I/O errors, not broker `error_code`). `assignment` is Java `assignment`
(`assigned_partitions` is the same list; `positions` is next fetch offset).
`max.poll.records` caps
how many records one `fetch` returns; the rest stay buffered.
`fetch.max.bytes` (`ConsumerConfig::max_bytes` / `fetch_max_bytes`) and
`max.partition.fetch.bytes` (`max_partition_fetch_bytes`) are independent;
`max_bytes()` sets both (default 16 MiB each; Java is 50 MiB / 1 MiB).
`ConsumerGroup` joins a group, heartbeats, fetches, and can commit offsets.
`join_topics` (range), `join_sticky_topics`, `join_cooperative_sticky_topics`,
and `join_consumer_topics`
subscribe to several topics. Range and sticky assign each topic independently
among members who subscribed to it. Sticky rebalances load when a member joins.
Cooperative-sticky (KIP-429) keeps owned partitions until the owner revokes
them, then rejoins so the new owner can take them. `ConsumerConfig::group_instance_id` is
Kafka `group.instance.id` (static membership) on JoinGroup, Heartbeat, and
KIP-848 heartbeats (ConsumerGroupHeartbeat v0–v1; v1 client-generated member id). `ConsumerConfig::rack` is also sent on KIP-848 and ShareGroupHeartbeat.
`ConsumerConfig::auto_offset_reset` runs when OffsetFetch has no committed
offset (`Earliest` by default). `ConsumerConfig::allow_auto_create_topics` is
Kafka `allow.auto.create.topics` on Metadata (default `false`). `committed` / `committed_timeout` are OffsetFetch
for the current assignment (Java `committed` / `committed(Duration)`). `commit` / `commit_timeout` are Java `commitSync` / `commitSync(Duration)`. `commit_offsets` commits caller-chosen offsets
([`TopicPartition`](../src/consumer.rs) plus the next fetch offset).
`seek_with_metadata` takes [`OffsetAndMetadata`](../src/consumer.rs)
(Java `seek(TopicPartition, OffsetAndMetadata)`; Fetch `LastFetchedEpoch`
from the leader epoch; metadata string ignored).
`commit_with_metadata` sends [`OffsetAndMetadata`](../src/consumer.rs)
(leader epoch and a metadata string). `commit_with_metadata_timeout` is Java
`commitSync(Map, Duration)`. Pass
[`ConsumerRecords::next_offsets`](../src/consumer.rs) to match Java
`commitSync(records.nextOffsets())`. `committed` returns the same type.
`commit_async` / `commit_async_with` are Java `commitAsync` / `commitAsync(OffsetCommitCallback)`:
the OffsetCommit is queued and sent on the next poll, leave, close, or unsubscribe (no spawned task).
`commit_with_metadata_async` is Java `commitAsync(Map, …)`.
`current_lag` is high watermark minus position. `subscription` is the
topic list. `enforce_rebalance` / `enforce_rebalance_with` rejoin on the
next poll (Java `enforceRebalance` / `enforceRebalance(String)`; JoinGroup
v8+ Reason, default `"rebalance enforced by user"`).
`subscribe` / `unsubscribe` change the topic list without dropping the
handle. `subscribe_matching` / `join_matching` / `join_sticky_matching` /
`join_cooperative_sticky_matching` / `join_consumer_matching` are Java
`subscribe(Pattern)` (re-list cluster topics on poll when `metadata.max.age.ms`
elapses; names starting with `__` are skipped). Share groups have the same
`subscribe_matching` / `join_matching`. `group_metadata` is Java `ConsumerGroupMetadata`. `list_topics`
is cluster Metadata. `fetch_timeout` / `poll_timeout` are Java
`poll(Duration)`. [`Producer::send_offsets_to_transaction`](../src/producer.rs)
takes [`TopicPartition`](../src/consumer.rs).
[`Producer::send_offsets_with_metadata`](../src/producer.rs)
commits transactional offsets with epoch and a metadata string.
`enable.auto.commit` is off by default; a zero interval commits after every
`poll`. `ConsumerConfig::session_timeout` / `heartbeat_interval` control classic
JoinGroup and the heartbeat loop. `on_rebalance` is `(revoked, assigned)`.
`max.poll.interval.ms` errors on the next `poll` if exceeded (`Error::MaxPollInterval`)
and the heartbeat thread leaves the group.
`Producer::metrics` / `Consumer::metrics` / `ShareGroup::metrics` / `Admin::metrics` are counter snapshots
plus latency min/mean/max and p50/p99 over the last 1024 samples (produce-ack / fetch round / Admin RPC),
and per-topic rows on `ProducerMetrics::topics` / `ConsumerMetrics::topics` /
`ShareMetrics::topics`. `AdminMetrics` is Java `Admin.metrics()`.
`client_instance_id` is Java `clientInstanceId` (KIP-714).
`client_instance_id_timeout` is Java `clientInstanceId(Duration)` (GetTelemetrySubscriptions RPC deadline; cached after the first successful call).
`Consumer::wakeup` (and a cloneable [`WakeupHandle`](../src/consumer.rs)) interrupts
fetch. `ProducerConfig::interceptor` / `ConsumerConfig::interceptor` observe or rewrite
records. [`TopicPartition`](../src/consumer.rs) and `offsets_for_times` are Java
`offsetsForTimes` (`OffsetAndTimestamp.leader_epoch` is Java `getLeaderEpoch`).
leader epoch. `FetchedRecord.serialized_key_size` / `serialized_value_size`
match Java. `assign_many` / `assign_partitions` / `unassign` replace or drop a
manual assignment (`assign_partitions` is Java `assign(Collection)` and uses
`auto.offset.reset`).
`Consumer::close` / `Consumer::close_timeout` drop fetch connections.
`ConsumerGroup::close_timeout` / `ShareGroup::close_timeout` cap `leave`
(Java `close(Duration)`).
`Admin::close_timeout` is Java `close(Duration)` (unused; no LeaveGroup).
`ShareGroup` is KIP-932 queue sharing. `join_topics` subscribes to several
topics.
## Wire format notes (for people changing encode/decode)
- Request `ClientId` is always a classic nullable string, even on flexible headers.
- ApiVersions **response** header is never flexible. If you parse it as flexible you eat the error code.
- Produce throttle time comes **after** the topic array. Metadata throttle time comes first.
- Record batch magic 2 CRC is CRC32-C over bytes from attributes to the end.
- Record lengths are zigzag varints. Compact protocol lengths are unsigned varint of `n+1` (`0` means null).
- Without `InitProducerId`, producer id / epoch / sequence must be `-1`. Zero is a real id.
- InitProducerId v0–v1 are classic; v2–v5 are flexible (compact transactional id plus tagged fields; request header 2, response header 1). v3+ adds ProducerId / ProducerEpoch on the request (KIP-360; first init sends `-1` / `-1`). After UNKNOWN_PRODUCER_ID the idempotent producer bumps the epoch locally and retries. After UNKNOWN_PRODUCER_ID / INVALID_PRODUCER_EPOCH / INVALID_PRODUCER_ID_MAPPING, a transactional abort still EndTxn-aborts (a Produce that already completed `send` with that error does not fail abort) and re-inits with the last producer id and epoch when EndTxn is below v5 (EndTxn v5 already returns the bumped identity). `commit_transaction` still fails `flush` on the Produce error. v4 is PRODUCER_FENCED; v5 is TRANSACTION_ABORTABLE (KIP-890). ThrottleTimeMs is JSON `0+` (`encode_init_producer_id_response_with_throttle`; encode previously always wrote `0` on v1+ and omitted the field on v0; decode discarded it; convenience encode still writes `0`; official Java `InitProducerIdResponse.throttleTimeMs` / `InitProducerIdResponseData.throttleTimeMs`; Java `getErrorResponse` sets `throttleTimeMs` to `0` even when the argument is non-zero). `InitProducerIdResponse.error_counts` is Java `InitProducerIdResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). Empty-error v0 and v1 bodies match; v2, v3, v4, and v5 bodies match. Top-level ErrorCode is at bytes 4–5. Kafka 4.0 `validVersions` is `0-5`. This crate speaks 0–5. v6+ (KIP-939 2PC) is not spoken.
- `acks=0` means the broker sends no Produce response. Do not read one.
- This client uses Produce versions 3–12 (v3–v8 classic record bytes; v9–v12 are compact arrays/strings/bytes plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `1+` after Responses (`encode_produce_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Responses v3, v4, v5, v6, v7, and v8 bodies match; v9, v10, v11, and v12 bodies match. There is no top-level ErrorCode. Official Java `getErrorResponse` sets `throttleTimeMs` from the argument. v10+ adds partition CurrentLeader tagged field 0 (KIP-951; `LeaderId` INT32 + `LeaderEpoch` INT32 + nested tagged fields) and top-level NodeEndpoints tagged field 0 (compact array of `NodeId` INT32 + `Host` compact STRING + `Port` INT32 + `Rack` compact nullable STRING + nested tagged fields). When Produce fails with a retriable error and CurrentLeader names a known broker, the producer patches that partition’s leader cache and skips a Metadata refresh. Unknown CurrentLeader brokers are inserted from NodeEndpoints first, then applied the same way. v11 is TRANSACTION_ABORTABLE (same layout as v10). v12 is the same layout (KIP-890 Part 2 transaction V2). When the broker advertises v12, transactional produce skips AddPartitionsToTxn (the partition leader performs that work). Kafka 4.0 removed v0–v2. Kafka 4.0 `validVersions` is `3-12`. v13+ (topic IDs) is not spoken.
- Fetch v11 is classic (RackId is a non-nullable STRING). Request SessionId / SessionEpoch are v7+ (`FetchMetadata`; `encode_fetch_request` writes LEGACY; a non-LEGACY session is omitted below v7). ForgottenTopicsData is v7+ (`encode_fetch_request_with_forgotten`; `encode_fetch_request` writes empty; a non-empty list is omitted below v7; v13+ uses TopicId). Request MaxWaitMs is JSON `0+` (decode returns it; encode already takes `max_wait_ms`; ReplicaId is untagged through v14 then MaxWaitMs; v15+ MaxWaitMs is the first untagged field). Request MinBytes is JSON `0+` (decode returns it; encode already takes `min_bytes`). Request ReplicaId is JSON `0-14` (untagged INT32; `encode_fetch_request_with_replica_id`; encode previously always wrote `CONSUMER_REPLICA_ID` and decode discarded; convenience encode still writes `CONSUMER_REPLICA_ID`; v15+ omit even when non-default and decode fills `CONSUMER_REPLICA_ID`; not ReplicaState tagged field 1). Request partition LogStartOffset is JSON `5+` (INT64 after LastFetchedEpoch; encode writes `FetchPartition.log_start_offset`; below v5 omit even when non-default and decode fills `INVALID_LOG_START_OFFSET`; official Java `FetchRequest.PartitionData.logStartOffset`). v12–v17 are flexible (compact arrays/strings/bytes plus tagged fields; LastFetchedEpoch after FetchOffset; request header 2, response header 1). ThrottleTimeMs is JSON `1+` first field (`encode_fetch_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Responses v4, v5, and v6 bodies match; v7, v8, v9, v10, and v11 bodies match; v12, v13, v14, v15, v16, and v17 bodies match. Top-level ErrorCode is at bytes 4–5 on v7+. Official Java `getErrorResponse` sets `throttleTimeMs` from the argument. v13 replaces topic names with topic ids on request Topics, ForgottenTopics, and response Responses (KIP-516). v14 is the same layout as v13 (`OffsetMovedToTieredStorageException`, KIP-405). v15 drops untagged ReplicaId; ReplicaState is tagged field 1 (KIP-903; this crate omits it because consumer defaults are `-1` / `-1`). v16 is the same request as v15 (KIP-951). Partition CurrentLeader tagged field 1 (`LeaderId` INT32 + `LeaderEpoch` INT32 + nested tagged fields) is decoded from v12+; when Fetch fails with a retriable error and CurrentLeader names a known broker, the consumer patches that partition’s leader cache and retries without a Metadata refresh. Unknown CurrentLeader brokers are inserted from top-level NodeEndpoints tagged field 0 (v16+; same inner layout as Produce) first, then applied the same way. Preferred-replica redirects and DivergingEpoch seeks also retry without Metadata. v17 is the same consumer request as v16 (ReplicaDirectoryId tagged field 0 is follower-only and omitted). Kafka 4.0 removed v0–v3. Kafka 4.0 `validVersions` is `4-17`. This crate speaks 4–17. v18+ (KIP-1166 HighWatermark) is not spoken. This crate sends LastFetchedEpoch from the last consumed record-batch leader epoch (`-1` after assign/seek until a batch is consumed, and from OffsetFetch `leader_epoch` on group assign). Fetch v12+ DivergingEpoch tagged field 0 (`Epoch` INT32 + `EndOffset` INT64 + nested tagged fields) is decoded; when present the consumer seeks to that end offset and retries without waiting `retry.backoff.ms`. Partition SnapshotId tagged field 2 (`EndOffset` INT64 then `Epoch` INT32 + nested tagged fields; JSON field order is the reverse of DivergingEpoch) is decoded from v12+; omitted fills `-1` / `-1`. Below v12 encode omits SnapshotId even when the body is non-default. This is not the FetchSnapshot API and does not start those RPCs.
- ListOffsets v4+ has `current_leader_epoch` before timestamp. The v4+ response has `leader_epoch` after offset. v1–v5 are classic; v6–v10 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). Request ReplicaId is JSON `0+` (INT32 first field; `encode_list_offsets_topics_request_with_replica_id`; encode previously always wrote `CONSUMER_REPLICA_ID` and decode discarded; convenience encode still writes `CONSUMER_REPLICA_ID`; official Java `ListOffsetsRequest.replicaId()`). v7 is MAX_TIMESTAMP `-3` (KIP-734). v8 is EARLIEST_LOCAL `-4` (KIP-405). v9 is LATEST_TIERED `-5` (KIP-1005). v10 adds TimeoutMs after Topics (KIP-1075; this crate sends `request_timeout`, or the one-shot timeout from `list_offsets_timeout` / `list_offsets_with_isolation_timeout`). Kafka 4.0 `validVersions` is `1-10`. This crate speaks 1–10. v11+ is not spoken.
- OffsetForLeaderEpoch v0–v3 are classic; v4 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). v1 response adds LeaderEpoch. v2 adds CurrentLeaderEpoch on the request and ThrottleTimeMs on the response. ThrottleTimeMs is JSON `2+` (`encode_offset_for_leader_epoch_topics_response_with_throttle`; encode previously always wrote `0` on v2+ and decode discarded; convenience encode still writes `0`; below v2 encode omits it even when the body is non-zero and decode fills `0`). Empty-Topics v0 and v1 bodies match; v2 and v3 bodies match; v4 is compact. There is no top-level ErrorCode. Official Java `getErrorResponse` does not set `throttleTimeMs` (JSON default `0`). Request ReplicaId is JSON `3+` (INT32 first field; default `-2`; `encode_offset_for_leader_epoch_topics_request_with_replica_id`; encode previously always wrote `CONSUMER_REPLICA_ID` on v3+ and decode discarded; convenience encode still writes `CONSUMER_REPLICA_ID`; below v3 omit even when non-default and decode fills `DEBUGGING_REPLICA_ID`; official Java `OffsetsForLeaderEpochRequest.replicaId()`). Fetch `FENCED_LEADER_EPOCH` / `UNKNOWN_LEADER_EPOCH` recover with Topics/Partitions of N (one RPC per current leader). Kafka 4.0 `validVersions` is `2-4` (v0–v1 removed). This crate speaks 0–4. v5+ is not spoken.
- AddPartitionsToTxn v0–v2 are classic; v3 is flexible (compact strings/arrays plus tagged fields on topics / top-level; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_add_partitions_to_txn_topics_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-ResultsByTopicV3AndBelow v0, v1, and v2 bodies match; v3 is compact. There is no top-level ErrorCode on spoken versions. This crate speaks 0–3. v4+ (batched transactions, KIP-890 broker layout) is not spoken.
- AddOffsetsToTxn v0–v2 are classic; v3–v4 are flexible (compact strings plus tagged fields; request header 2, response header 1). v4 is TRANSACTION_ABORTABLE (KIP-890; same layout as v3). ThrottleTimeMs is JSON `0+` (`encode_add_offsets_to_txn_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Request ProducerId is JSON `0+` (INT64 after TransactionalId; decode previously discarded it; encode already takes `producer_id`; official Java `AddOffsetsToTxnRequestData.producerId`; not EndTxn response ProducerId / InitProducerId / TxnOffsetCommit ProducerId / AddPartitionsToTxn ProducerId / WriteTxnMarkers ProducerId / ProducerEpoch). Request ProducerEpoch is JSON `0+` (INT16 after ProducerId; decode previously discarded it; encode already takes `producer_epoch`; official Java `AddOffsetsToTxnRequestData.producerEpoch`; not EndTxn response ProducerEpoch / InitProducerId / TxnOffsetCommit ProducerEpoch / AddPartitionsToTxn ProducerEpoch / WriteTxnMarkers ProducerEpoch / ProducerId). Empty-error v0, v1, and v2 bodies match; v3 and v4 bodies match. Top-level ErrorCode is at bytes 4–5. `AddOffsetsToTxnResponse.error_counts` is Java `AddOffsetsToTxnResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). Kafka 4.0 `validVersions` is `0-4`. This crate speaks 0–4. v5+ is not spoken.
- EndTxn v0–v2 are classic; v3–v5 are flexible (compact strings plus tagged fields; request header 2, response header 1). v4 is TRANSACTION_ABORTABLE (KIP-890; same request layout as v3). v5 adds ProducerId / ProducerEpoch on the response (KIP-890 Part 2). ThrottleTimeMs is JSON `0+` (`encode_end_txn_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-error v0, v1, and v2 bodies match; v3 and v4 bodies match; v5 adds ProducerId / ProducerEpoch. Top-level ErrorCode is at bytes 4–5. `EndTxnResponse.error_counts` is Java `EndTxnResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). After a successful EndTxn v5 the producer stores those fields when `producer_id >= 0` and clears per-partition sequences if the identity changed. Kafka 4.0 `validVersions` is `0-5`. This crate speaks 0–5. v6+ is not spoken.
- TxnOffsetCommit v0–v2 are classic (v2 adds committed leader epoch). v3–v5 are flexible (compact strings/arrays plus tagged fields on partitions / topics / top-level; request header 2, response header 1) and add GenerationId / MemberId / GroupInstanceId. `send_offsets_for_group` sends those fields from `ConsumerGroupMetadata`; `send_offsets_to_transaction` sends `-1` / empty / null. v4 is TRANSACTION_ABORTABLE (KIP-890; same layout as v3). v5 is the same layout (KIP-890 Part 2 transaction V2). When the broker advertises v5, `send_offsets_*` skips AddOffsetsToTxn (the group coordinator performs that work). ThrottleTimeMs is JSON `0+` (`encode_txn_offset_commit_topics_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Request ProducerId is JSON `0+` (INT64 after GroupId; decode previously discarded it; encode already takes `producer_id`; official Java `TxnOffsetCommitRequestData.producerId`; not AddOffsetsToTxn ProducerId / EndTxn response ProducerId / InitProducerId / AddPartitionsToTxn ProducerId / WriteTxnMarkers ProducerId / ProducerEpoch). Request ProducerEpoch is JSON `0+` (INT16 after ProducerId; decode previously discarded it; encode already takes `producer_epoch`; official Java `TxnOffsetCommitRequestData.producerEpoch`; not AddOffsetsToTxn ProducerEpoch / EndTxn response ProducerEpoch / InitProducerId / AddPartitionsToTxn ProducerEpoch / WriteTxnMarkers ProducerEpoch / ProducerId). Empty-Topics v0, v1, and v2 bodies match; v3, v4, and v5 bodies match. There is no top-level ErrorCode. Kafka 4.0 `validVersions` is `0-5`. This crate speaks 0–5. v6+ is not spoken.
- FindCoordinator v1–v2 are classic (Key + KeyType). v3 is flexible (compact key plus tagged fields; request header 2, response header 1). v4–v6 replace Key with CoordinatorKeys and the top-level coordinator fields with Coordinators (KIP-699; v5 is TRANSACTION_ABORTABLE; v6 is share groups, KIP-932). ThrottleTimeMs is JSON `1+` (`encode_find_coordinator_response_coordinators_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-error v1 and v2 bodies match; v3 is compact; empty-Coordinators v4, v5, and v6 bodies match. Top-level ErrorCode is at bytes 4–5 on v1–v3; v4+ has no top-level ErrorCode. `FindCoordinatorResponse.has_error` is Java `FindCoordinatorResponse.hasError` (top-level `errorCode` only; v4+ coordinator codes are ignored). Official Java `getErrorResponse` sets `throttleTimeMs` from the argument on v2+; v1 leaves the JSON default `0`. `list_consumer_group_offsets_for_groups`, `describe_groups`, `delete_groups`, `consumer_group_describe`, `share_group_describe`, `describe_share_group_offsets`, and `describe_transactions` send CoordinatorKeys of N on v4+ (one FindCoordinator per coordinator-discovery retry) and one subsequent RPC per coordinator. Brokers that only speak v1–v3 get one FindCoordinator per key. This crate speaks 1–6. v0 (no KeyType) and v7+ are not spoken.
- OffsetCommit v2–v7 are classic; v8–v9 are flexible (compact strings/arrays plus tagged fields on partitions / topics / top-level; request header 2, response header 1). Official JSON: v3 and v4 match v2 (RetentionTimeMs; this crate sends `-1`). v5 drops retention. v6 CommittedLeaderEpoch. v7 GroupInstanceId. Request GenerationIdOrMemberEpoch is JSON `1+` (INT32 after GroupId; decode previously discarded it; encode already takes `generation_id`; official Java `OffsetCommitRequestData.generationIdOrMemberEpoch` / `OffsetCommitRequest.DEFAULT_GENERATION_ID`; not SyncGroup GenerationId / Heartbeat GenerationId / JoinGroup response GenerationId / TxnOffsetCommit GenerationId). v3+ ThrottleTimeMs. v9 is KIP-848 error codes (same layout as v8). Kafka 4.0 `validVersions` is `2-9` (v0–v1 removed). This crate speaks 2–9. v0–v1 and v10+ are not spoken. `alter_consumer_group_offsets_timeout` is Java `AlterConsumerGroupOffsetsOptions.timeoutMs` (RPC deadline; OffsetCommit has no TimeoutMs).
- OffsetFetch v1–v5 are classic; v6–v9 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). Official JSON: v3, v4, and v5 match v2 on the request (GroupId, Topics). v2 nullable Topics and top-level ErrorCode (`None` Topics is every committed partition; `Admin::list_all_consumer_group_offsets`). v3 ThrottleTimeMs. v5 CommittedLeaderEpoch (decode fills `-1` below v5). v7 RequireStable (`true` when `isolation.level` is read-committed; `list_consumer_group_offsets_with` / `list_all_consumer_group_offsets_with` send the Admin flag). `list_all_consumer_group_offsets_timeout` / `list_consumer_group_offsets_for_groups_timeout` are Java `ListConsumerGroupOffsetsOptions.timeoutMs` (RPC deadline; OffsetFetch has no TimeoutMs). v8 replaces GroupId / Topics with Groups (KIP-709; `list_consumer_group_offsets_for_groups` sends one Groups array per coordinator). v9 adds MemberId / MemberEpoch on each group (KIP-848; classic groups send null / `-1`). Kafka 4.0 `validVersions` is `1-9` (v0 removed). This crate speaks 1–9. v0 and v10+ (topic IDs) are not spoken.
- OffsetDelete v0 is classic (Apache JSON `flexibleVersions: "none"`). ThrottleTimeMs is JSON `0+` (`encode_offset_delete_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`) but **after** ErrorCode (not first). Empty-Topics only one version. Top-level ErrorCode is at bytes 0–1 (throttle occupies bytes 2–5). Official Java `getErrorResponse` sets `throttleTimeMs` from the argument. Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- Heartbeat v0–v3 are classic; v4 is flexible (compact strings plus tagged fields; request header 2, response header 1). Official JSON: v1 and v2 match v0. v1+ ThrottleTimeMs. v3 GroupInstanceId. `HeartbeatResponse.error_counts` is Java `HeartbeatResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). Kafka 4.0 `validVersions` is `0-4`. This crate speaks 0–4. v5+ is not spoken.
- SyncGroup v0–v3 are classic; v4–v5 are flexible (compact strings/bytes/arrays plus tagged fields; request header 2, response header 1). Official JSON: v1 and v2 match v0. v1+ ThrottleTimeMs. Request GenerationId is JSON `0+` (INT32 after GroupId; decode previously discarded it; encode already takes `generation_id`; official Java `SyncGroupRequestData.generationId`; not OffsetCommit GenerationId / Heartbeat GenerationId / JoinGroup response GenerationId). v3 GroupInstanceId. v5 ProtocolType / ProtocolName (KIP-559; this crate sends `"consumer"` and the selected assignor). `SyncGroupResponse.error_counts` is Java `SyncGroupResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). Kafka 4.0 `validVersions` is `0-5`. This crate speaks 0–5. v6+ is not spoken.
- JoinGroup v2–v5 are classic; v6–v9 are flexible (compact strings/bytes/arrays plus tagged fields; request header 2, response header 1). Official JSON: v2 and v3 match v1 (RebalanceTimeoutMs). Request SessionTimeoutMs is JSON `0+` (decode returns it; encode already takes `session_timeout_ms`). Request RebalanceTimeoutMs is JSON `1+` (spoken v2–v9 always on the wire; decode returns it; encode writes `rebalance_timeout_ms`; official Java `JoinGroupRequestData.rebalanceTimeoutMs`; classic consumer sends `max.poll.interval.ms`). Request ProtocolType is JSON `0+` (decode returns it last; encode already takes `protocol_type`; official Java `JoinGroupRequestData.protocolType`). v4 second join with assigned id. v5 request GroupInstanceId. Response member GroupInstanceId is JSON `5+` (nullable STRING after member MemberId; encode previously always wrote null and decode discarded; below v5 encode omits it even when the body has an instance id and decode fills `None`; official Java `JoinGroupResponseData.JoinGroupResponseMember.groupInstanceId`; not JoinGroup request GroupInstanceId / SyncGroup GroupInstanceId / Heartbeat GroupInstanceId / OffsetCommit GroupInstanceId / LeaveGroup GroupInstanceId). v7 response adds ProtocolType (KIP-559) and nullable ProtocolName. v8 adds Reason (KIP-800; first join is null; `enforce_rebalance` / `enforce_rebalance_with` send the reason). v9 adds SkipAssignment; when true the leader does not run the assignor. SkipAssignment is JSON `9+` (`encode_join_group_response_with_skip_assignment`; encode previously always wrote `false`; below v9 omit even when true and decode fills `false`; convenience encode still writes `false`; official Java `JoinGroupResponseData.skipAssignment`; Java `getErrorResponse` does not set it). ThrottleTimeMs is JSON `2+` (`encode_join_group_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Members v2, v3, v4, and v5 bodies match; v6 is compact; v7 and v8 bodies match; v9 adds SkipAssignment. Top-level ErrorCode is at bytes 4–5. `JoinGroupResponse.error_counts` is Java `JoinGroupResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). Official Java `getErrorResponse` sets `throttleTimeMs` from the argument. `join()` / `join_sticky` / `join_cooperative_sticky` send Protocols of 1. `join_with_assignors` sends Protocols of N (Java `partition.assignment.strategy`); the client assigns with the JoinGroup response ProtocolName. Kafka 4.0 `validVersions` is `2-9` (v0–v1 removed). This crate speaks 2–9. v0–v1 and v10+ are not spoken.
- LeaveGroup v0–v3 are classic; v4–v5 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). v0–v2 are GroupId + MemberId (v1 and v2 match v0). v1+ ThrottleTimeMs. v3 Members + GroupInstanceId. v5 Reason (KIP-800). Kafka 4.0 `validVersions` is `0-5`. This crate speaks 0–5. Classic `ConsumerGroup::leave` / `close` send `"the consumer is being closed"`; `unsubscribe` sends `"the consumer unsubscribed from all topics"`; `max.poll.interval.ms` expiry sends `"consumer poll timeout has expired."`. Admin `removeMembersFromConsumerGroup` stays v3–v5 with `"member was removed by an admin"`. v6+ is not spoken.
- ConsumerGroupHeartbeat v0–v1 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). v1 adds SubscribedTopicRegex after SubscribedTopicNames (KIP-848) and requires the consumer to generate its own MemberId (KIP-1082; Kafka `Uuid` URL-safe Base64). `join_consumer_matching` still expands topics locally (`Fn(&str) -> bool`) and sends SubscribedTopicNames; the regex field is null. Request RebalanceTimeoutMs is JSON `0+` (INT32 after RackId; encode writes `rebalance_timeout_ms`; decode previously discarded it; JSON default `-1` means unchanged; join sends `max.poll.interval.ms`). Request ServerAssignor is JSON `0+` (nullable compact STRING after SubscribedTopicRegex on v1 / after SubscribedTopicNames on v0; encode writes `server_assignor`; decode previously discarded it; JSON default null means unused or unchanged; this crate still sends null). ThrottleTimeMs is JSON `0+` (`ConsumerGroupHeartbeatResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded). `ConsumerGroupHeartbeatResponse.error_counts` is Java `ConsumerGroupHeartbeatResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; assignment is not counted). Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ is not spoken. v1 response matches v0.
- ConsumerGroupDescribe v0–v1 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). Request layout is the same on v0 and v1. Response v1 adds MemberType INT8 (KIP-1099). ThrottleTimeMs is JSON `0+` (`encode_consumer_group_describe_response_with_throttle`; `encode_consumer_group_describe_response` writes `0`; encode previously always wrote `0` and decode discarded). `ConsumerGroupDescribeRequest.error_response` is Java `ConsumerGroupDescribeRequest.getErrorResponse` (copies group ids through `error_described_group_list`; ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience encode writes `0`; official Java sets `throttleTimeMs` from the argument; request IncludeAuthorizedOperations is not copied). Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ is not spoken. ErrorCode is per-group. `consumer_group_describe` sends FindCoordinator v4+ CoordinatorKeys of N (KIP-699) and one ConsumerGroupDescribe RPC per coordinator. `consumer_group_describe_timeout` is the crate-first RPC deadline (ConsumerGroupDescribe has no TimeoutMs). Java `describeConsumerGroups` (`Admin::describe_consumer_groups`) tries this API first and falls back to DescribeGroups on `UNSUPPORTED_VERSION` (35) or `GROUP_ID_NOT_FOUND` (69), or when the broker does not advertise api 69.
- ShareGroupHeartbeat v0–v1 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). Same fields. Request RackId is JSON `0+` (nullable compact STRING after MemberEpoch; encode writes `rack_id`; decode previously discarded it; JSON default null means not provided or unchanged; this crate sends `ConsumerConfig::rack`). ThrottleTimeMs is JSON `0+` (`ShareGroupHeartbeatResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded). `ShareGroupHeartbeatResponse.error_counts` is Java `ShareGroupHeartbeatResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; assignment is not counted). Kafka 4.0 `validVersions` is `"0"` (`latestVersionUnstable`). Kafka 4.1 `validVersions` is `"1"` (v0 removed). This crate speaks 0–1. v2+ is not spoken. v1 response matches v0.
- ShareGroupDescribe v0–v1 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). Same fields. ThrottleTimeMs is JSON `0+` (`encode_share_group_describe_response_with_throttle`; `encode_share_group_describe_response` writes `0`; encode previously always wrote `0` and decode discarded). `ShareGroupDescribeRequest.error_response` is Java `ShareGroupDescribeRequest.getErrorResponse` (copies group ids through `error_described_group_list`; ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience encode writes `0`; official Java sets `throttleTimeMs` from the argument; request IncludeAuthorizedOperations is not copied). Kafka 4.0 `validVersions` is `"0"` (`latestVersionUnstable`). Kafka 4.1 `validVersions` is `"1"` (v0 removed). This crate speaks 0–1. v2+ is not spoken. v1 response matches v0. ErrorCode is per-group. Official Java `DescribeShareGroupsHandler` uses `CoordinatorType.GROUP`. `share_group_describe` / `describe_share_groups` send FindCoordinator v4+ CoordinatorKeys of N (KIP-699) and one ShareGroupDescribe RPC per coordinator. `share_group_describe_timeout` / `describe_share_groups_timeout` are Java `DescribeShareGroupsOptions.timeoutMs` (RPC deadline; ShareGroupDescribe has no TimeoutMs).
- ShareFetch v0–v1 are flexible (compact strings/arrays/bytes plus tagged fields; request header 2, response header 1). Kafka 4.0 `validVersions` is `"0"` (`latestVersionUnstable`). Kafka 4.1 `validVersions` is `"1"` (v0 removed). v0 PartitionMaxBytes on each partition. v1 MaxRecords / BatchSize after MaxBytes (JSON `1+`; `encode_share_fetch_request_with_batch_size`; `encode_share_fetch_request` still writes BatchSize as MaxRecords; v0 omits even when non-zero and decode fills 0; no PartitionMaxBytes). MaxWaitMs is JSON `0+` (decode returns it; encode already takes `max_wait_ms`). MinBytes is JSON `0+` (decode returns it; encode already takes `min_bytes`). MaxBytes is JSON `0+` (decode returns it; encode already takes `max_bytes`; JSON default `0x7fffffff`). Records is JSON `records` (Kafka 4.0 `nullableVersions` `0+`; Kafka 4.1 `nullableVersions` `0` only; decode accepts compact null as empty on v0; v1 null is `Error::protocol` Java generated `non-nullable field records was serialized as null`; encode still writes `MemoryRecords.EMPTY` not null). AcquisitionLockTimeoutMs after ErrorMessage (JSON `1+`; `encode_share_fetch_response_with_acquisition_lock_timeout`; `encode_share_fetch_response` still writes 15000; v0 omits even when non-zero and decode fills 0; error-path stays 0). ForgottenTopicsData is JSON `0+` (`encode_share_fetch_request_with_forgotten`; `encode_share_fetch_request` writes empty; duplicate partition indexes are kept; TopicId UUID, no name). Response NodeEndpoints is JSON `0+` (untagged compact array of `NodeId` INT32 + `Host` compact STRING + `Port` INT32 + `Rack` compact nullable STRING + nested tagged fields; `encode_share_fetch_response_with_endpoints`; `encode_share_fetch_response` writes empty; not Fetch v16 tagged field 0). Partition CurrentLeader is JSON `0+` (untagged nested `LeaderIdAndEpoch`: `LeaderId` INT32 + `LeaderEpoch` INT32 + nested tagged fields; encode writes the partition fields; `partition_response` fills 0/0; not Fetch v12+ tagged field 1). Partition ErrorMessage is JSON `0+` (nullable compact STRING; encode writes the partition field; `partition_response` fills null; not the top-level ErrorMessage). Partition AcknowledgeErrorCode is JSON `0+` (encode writes the partition field; `partition_response` fills 0; not fetch `ErrorCode`; JSON lists `INVALID_RECORD_STATE` as acknowledge-only). Partition AcknowledgeErrorMessage is JSON `0+` (nullable compact STRING; encode writes the partition field; `partition_response` fills null; not fetch `ErrorMessage`). ThrottleTimeMs is JSON `0+` (`encode_share_fetch_response_with_throttle`; `encode_share_fetch_response` writes `0`; `encode_share_fetch_error` still writes `0`; `encode_share_fetch_error_with_throttle` round-trips a non-zero value; Java `ShareFetchRequest.getErrorResponse` / `ShareFetchResponse.of` sets `throttleTimeMs`; empty Responses; v1 AcquisitionLockTimeoutMs stays `0` on that path). Top-level ErrorCode is JSON `0+` (`encode_share_fetch_response_with_error_code`; `encode_share_fetch_response` still writes `0`; decode returns it and does not fail on a non-zero code). Top-level ErrorMessage is JSON `0+` (`encode_share_fetch_response_with_error_message`; `encode_share_fetch_response` writes null; not partition ErrorMessage). This crate speaks 0–1. v2+ is not spoken.
- ShareAcknowledge v0–v1 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). Same fields. Kafka 4.0 `validVersions` is `"0"` (`latestVersionUnstable`). Kafka 4.1 `validVersions` is `"1"` (v0 removed). This crate speaks 0–1. v2+ is not spoken. v1 response matches v0. Response NodeEndpoints is JSON `0+` (untagged compact array of `NodeId` INT32 + `Host` compact STRING + `Port` INT32 + `Rack` compact nullable STRING + nested tagged fields; `encode_share_acknowledge_topics_response_with_endpoints`; `encode_share_acknowledge_topics_response` writes empty; not Fetch v16 tagged field 0). Partition CurrentLeader is JSON `0+` (untagged nested `LeaderIdAndEpoch`: `LeaderId` INT32 + `LeaderEpoch` INT32 + nested tagged fields; encode writes the partition fields; `partition_response` fills 0/0; not Fetch v12+ tagged field 1). Partition ErrorMessage is JSON `0+` (nullable compact STRING; encode writes the partition field; `partition_response` fills null; not the top-level ErrorMessage). ThrottleTimeMs is JSON `0+` (`encode_share_acknowledge_topics_response_with_throttle`; `encode_share_acknowledge_topics_response` writes `0`; v0 and v1 bodies match). Top-level ErrorMessage is JSON `0+` (`encode_share_acknowledge_topics_response_with_error_message`; `encode_share_acknowledge_topics_response` writes null; not partition ErrorMessage; v0 and v1 bodies match).
- DescribeShareGroupOffsets v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_describe_share_group_offsets_response_with_throttle`; `encode_describe_share_group_offsets_response` writes `0`; encode previously always wrote `0` and decode discarded). This crate speaks 0. v1+ (Lag, KIP-1226) is not spoken. Group-level ErrorCode is per-group. `DescribeShareGroupOffsetsResponse.error_counts` is Java `DescribeShareGroupOffsetsResponse.errorCounts` (group-level non-`NONE` last-wins on GroupId, plus every partition-level code including `NONE`; there is no top-level ErrorCode). `DescribeShareGroupOffsetsResponse.has_group_error` is Java `DescribeShareGroupOffsetsResponse.hasGroupError` (`true` when any matching `groupId` has a non-`NONE` group-level code; partition-level codes are ignored; Java `groupError` returns `Throwable` and is not mapped). `DescribeShareGroupOffsetsRequest.error_response` is Java `DescribeShareGroupOffsetsRequest.getErrorResponse` (copies group ids; empty Topics; ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience encode writes `0`; official Java sets `throttleTimeMs` from the argument; request Topics are not copied). `DescribeShareGroupOffsetsRequest.error_described_group` is Java `DescribeShareGroupOffsetsRequest.getErrorDescribedGroup` (one GroupId + ErrorCode; empty Topics; ErrorMessage JSON-null; official Java sets English `Errors.message`). Official Java `ListShareGroupOffsetsHandler` uses `CoordinatorType.GROUP`. `describe_share_group_offsets` / `list_share_group_offsets` send FindCoordinator v4+ CoordinatorKeys of N (KIP-699) and one DescribeShareGroupOffsets RPC per coordinator. `describe_share_group_offsets_timeout` / `list_share_group_offsets_timeout` are Java `ListShareGroupOffsetsOptions.timeoutMs` (RPC deadline; DescribeShareGroupOffsets has no TimeoutMs). `alter_share_group_offsets_timeout` / `delete_share_group_offsets_timeout` are Java `AlterShareGroupOffsetsOptions` / `DeleteShareGroupOffsetsOptions.timeoutMs` (RPC deadline; these RPCs have no TimeoutMs).
- AlterShareGroupOffsets v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`AlteredShareGroupOffsets.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `AlteredShareGroupOffsets::new` fills `0`). `AlteredShareGroupOffsets.error_counts` is Java `AlterShareGroupOffsetsResponse.errorCounts` (top-level ErrorCode plus each partition-level code, including `NONE`; topics have no ErrorCode). `AlterShareGroupOffsetsRequest.error_response` is Java `AlterShareGroupOffsetsRequest.getErrorResponse` (empty Responses; ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument and the English `Errors.message`; request GroupId / Topics are not copied). Kafka 4.1 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. ErrorCode is top-level after throttle.
- DeleteShareGroupOffsets v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`DeletedShareGroupOffsets.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `DeletedShareGroupOffsets::new` fills `0`). `DeletedShareGroupOffsets.error_counts` is Java `DeleteShareGroupOffsetsResponse.errorCounts` (top-level ErrorCode plus each topic-level code, including `NONE`; there is no partition ErrorCode). `DeleteShareGroupOffsetsRequest.error_response` is Java `DeleteShareGroupOffsetsRequest.getErrorResponse` (empty Responses; ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument and the English `Errors.message`; request GroupId / Topics are not copied). Kafka 4.1 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. ErrorCode is top-level after throttle.
- DescribeTopicPartitions v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`DescribeTopicPartitionsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `DescribeTopicPartitionsResponse::new` fills `0`). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. There is no top-level ErrorCode.
- ListConfigResources v0–v1 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`ListConfigResourcesResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `ListConfigResourcesResponse::new` fills `0`). `ListConfigResourcesResponse.error_counts` is Java `ListConfigResourcesResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; config resources are not counted). `ListConfigResourcesResponse.error` is Java `ListConfigResourcesResponse.error` (`ApiError` from the top-level ErrorCode; unknown codes become `UNKNOWN_SERVER_ERROR`; JSON has no ErrorMessage; this crate fills a null message). `ListConfigResourcesRequest.error_response` is Java `ListConfigResourcesRequest.getErrorResponse` (empty ConfigResources; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument). `ListConfigResourcesRequest.supported_resource_types` is Java `ListConfigResourcesRequest.supportedResourceTypes` (v0 `CLIENT_METRICS` only; v1 `TOPIC` / `BROKER` / `BROKER_LOGGER` / `CLIENT_METRICS` / `GROUP`; `UNKNOWN` is not included). `ListConfigResourcesRequest.build` is Java `ListConfigResourcesRequest.Builder.build` (v0 ResourceTypes must be exactly `CLIENT_METRICS`; Java `HashSet`; encode still omits ResourceTypes on v0). `ListConfigResourcesResponse.config_resources` is Java `ListConfigResourcesResponse.configResources` (`ConfigResource` via `Type.forId`; unknown ids are `UNKNOWN`). Kafka 4.0 `validVersions` is `"0"` (ListClientMetricsResources). Kafka 4.1 `validVersions` is `"0-1"` (v1 ResourceType, KIP-1142). This crate speaks 0–1. v2+ is not spoken. Empty-resource bodies: v0 == v1. ErrorCode is top-level after throttle.
- GetTelemetrySubscriptions v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`GetTelemetrySubscriptionsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `GetTelemetrySubscriptionsResponse::new` fills `0`). `GetTelemetrySubscriptionsResponse.error_counts` is Java `GetTelemetrySubscriptionsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; ClientInstanceId / compression types / requested metrics are not counted). `GetTelemetrySubscriptionsResponse.has_error` is Java `GetTelemetrySubscriptionsResponse.hasError` (`error() != NONE`). `GetTelemetrySubscriptionsRequest.error_response` is Java `GetTelemetrySubscriptionsRequest.getErrorResponse` (JSON defaults for ClientInstanceId / subscription fields; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. ErrorCode is top-level after throttle.
- PushTelemetry v0 is flexible (compact strings/bytes plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`PushTelemetryResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `PushTelemetryResponse::new` fills `0`). `PushTelemetryResponse.error_counts` is Java `PushTelemetryResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). `PushTelemetryResponse.has_error` is Java `PushTelemetryResponse.hasError` (`error() != NONE`). `PushTelemetryRequest.error_response` is Java `PushTelemetryRequest.getErrorResponse` (ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java `getErrorResponse` delegates to `errorResponse` and sets `throttleTimeMs` from the argument). `PushTelemetryRequest.metrics_content_type` is Java `PushTelemetryRequest.metricsContentType` (`OTLP`; the request has no content-type field). `PushTelemetryRequest.metrics_data` is Java `PushTelemetryRequest.metricsData` (`NONE` returns stored bytes; gzip / snappy / lz4 decompress via `ClientTelemetryUtils.decompress`; zstd is not spoken; unknown ids are `IllegalArgumentException`). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. ErrorCode is top-level after throttle.
- AssignReplicasToDirs v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`AssignReplicasToDirsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `AssignReplicasToDirsResponse::new` fills `0`). `AssignReplicasToDirsRequest.error_response` is Java `AssignReplicasToDirsRequest.getErrorResponse` (empty Directories; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request BrokerId / BrokerEpoch / Directories are not copied). `AssignReplicasToDirsRequest.MAX_ASSIGNMENTS_PER_REQUEST` is Java `AssignReplicasToDirsRequest.MAX_ASSIGNMENTS_PER_REQUEST` (`2250`; encode does not enforce the cap). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. ErrorCode is top-level after throttle.
- SaslHandshake v0–v1 are classic (Apache JSON `flexibleVersions: "none"`). Same fields. v1 enables SaslAuthenticate. `SaslHandshakeResponse.error_counts` is Java `SaslHandshakeResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; Mechanisms are not counted). Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ is not spoken (KAFKA-9577).
- SaslAuthenticate v0–v1 are classic. v2 is flexible (compact bytes/strings plus tagged fields; request header 2, response header 1). v0 and v1 request match (AuthBytes). v1+ SessionLifetimeMs. `SaslAuthenticateResponse.error_counts` is Java `SaslAuthenticateResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; AuthBytes / ErrorMessage / SessionLifetimeMs are not counted). Kafka 4.0 `validVersions` is `0-2`. This crate speaks 0–2. v3+ is not spoken.
- ListTransactions v0–v1 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v1 adds DurationFilter INT64 after ProducerIdFilters (KIP-994; `< 0` means no filter). ThrottleTimeMs is JSON `0+` (`ListTransactionsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` fills `0`). Empty-TransactionStates v0 and v1 bodies match. Top-level ErrorCode is at bytes 4–5. `ListTransactionsResponse.error_counts` is Java `ListTransactionsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `updateErrorCounts`; unknown state filters and listings are not counted). `ListTransactionsRequest.error_response` is Java `ListTransactionsRequest.getErrorResponse` (empty UnknownStateFilters / TransactionStates; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request StateFilters / ProducerIdFilters / DurationFilter are not copied). Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ (TransactionalIdPattern) is not spoken. v1 response matches v0. `list_transactions_timeout` / `list_transactions_with_duration_timeout` are Java `ListTransactionsOptions.timeoutMs` (RPC deadline; ListTransactions has no TimeoutMs; `duration_ms` is DurationFilter, not TimeoutMs).
- DescribeTransactions v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_describe_transactions_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-TransactionStates only one version. There is no top-level ErrorCode. Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken. ErrorCode is per transactional.id. `describe_transactions` sends FindCoordinator v4+ CoordinatorKeys of N (`key_type=1`) and one DescribeTransactions RPC per coordinator. `describe_transactions_timeout` is Java `DescribeTransactionsOptions.timeoutMs` (RPC deadline; DescribeTransactions has no TimeoutMs).
- CreateTopics v0–v4 are classic. v5–v7 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v5 returns NumPartitions / ReplicationFactor / Configs (KIP-525). v6 is the same layout (KIP-599 THROTTLING_QUOTA_EXCEEDED). v7 adds TopicId UUID after Name (KIP-516). `create_topics_timeout` is Java `CreateTopicsOptions.timeoutMs` (RPC deadline and TimeoutMs). `create_topics_with_quota_retry` is Java `CreateTopicsOptions.retryOnQuotaViolation` (default `true`; KIP-599; retries only topics that return `THROTTLING_QUOTA_EXCEEDED`). `NewTopic.with_assignments` is Java `NewTopic(String, Map<Integer, List<Integer>>)` (NumPartitions / ReplicationFactor `-1`; empty Assignments is `NewTopic(String, int, short)` — not null; official JSON is not nullable). `NewTopic.broker_defaults` is Java `NewTopic(String, Optional.empty(), Optional.empty())` (KIP-464; `-1` / `-1` with empty Assignments; v4+ uses broker `num.partitions` / `default.replication.factor`). `NewTopic.configs` is Java `NewTopic.configs(Map)` (replaces prior `config` entries). Kafka 4.0 `validVersions` is `2-7` (v0–v1 removed). This crate speaks 0–7. v8+ is not spoken.
- DeleteTopics v0–v3 are classic (TopicNames + TimeoutMs). v4–v6 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v5 adds ErrorMessage (KIP-599). v6 replaces TopicNames with Topics of Name + TopicId (KIP-516; name-based deletes send a zero UUID; `delete_topics_by_id` is Java `deleteTopics(TopicCollection.ofTopicIds)` with null Name). `delete_topics_timeout` is Java `DeleteTopicsOptions.timeoutMs` (RPC deadline and TimeoutMs). `delete_topics_with_quota_retry` / `delete_topics_by_id_with_quota_retry` are Java `DeleteTopicsOptions.retryOnQuotaViolation` (default `true`; KIP-599; retries only topics that return `THROTTLING_QUOTA_EXCEEDED`). Kafka 4.0 `validVersions` is `1-6` (v0 removed). This crate speaks 0–6. v7+ is not spoken.
- DescribeConfigs v0–v3 are classic. ThrottleTimeMs is JSON `0+` (on the wire for v0–v4). `shouldClientThrottle` is v2+ (KIP-219). v1 adds IncludeSynonyms / ConfigSource / Synonyms. v2 is the same layout as v1. v3 adds IncludeDocumentation, ConfigType, and Documentation (KIP-226). v4 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). Kafka 4.0 `validVersions` is `1-4` (v0 removed). This crate speaks 0–4. v5+ is not spoken. `describe_configs_with_documentation` is Java `DescribeConfigsOptions.includeDocumentation`; v0–v2 omit the field. `describe_configs_timeout` / `describe_configs_with_documentation_timeout` are Java `DescribeConfigsOptions.timeoutMs` (RPC deadline; DescribeConfigs has no TimeoutMs).
- CreatePartitions v0–v1 are classic. v2–v3 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v3 is the same layout (KIP-599 THROTTLING_QUOTA_EXCEEDED). ThrottleTimeMs is JSON `0+` (`encode_create_partitions_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Results v0 and v1 bodies match; v2 and v3 bodies match. `create_partitions_timeout` is Java `CreatePartitionsOptions.timeoutMs` (RPC deadline and TimeoutMs). `create_partitions_with_quota_retry` is Java `CreatePartitionsOptions.retryOnQuotaViolation` (default `true`; KIP-599; retries only topics that return `THROTTLING_QUOTA_EXCEEDED`). `NewPartitions.with_assignments` is Java `increaseTo(int, List<List<Integer>>)` (null Assignments when omitted; the broker assigns replicas). Kafka 4.0 `validVersions` is `0-3`. This crate speaks 0–3. v4+ is not spoken.
- AlterPartitionReassignments v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`AlterPartitionReassignmentsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` / `::error` fill `0`). `alter_partition_reassignments_timeout` is Java `AlterPartitionReassignmentsOptions.timeoutMs` (RPC deadline and TimeoutMs). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- ListPartitionReassignments v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`ListPartitionReassignmentsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` / `::error` fill `0`). `topics = None` lists every ongoing reassignment. `list_partition_reassignments_all` is Java `listPartitionReassignments()`. `list_partition_reassignments_for` is Java `listPartitionReassignments(Set)`. `list_partition_reassignments_timeout` is Java `ListPartitionReassignmentsOptions.timeoutMs` (RPC deadline and TimeoutMs). `ListPartitionReassignmentsResponse.error_counts` is Java `ListPartitionReassignmentsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; topics / partitions are not counted; not `ListPartitionReassignmentsResponse.error` / AlterPartitionReassignments `errorCounts`). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- UpdateFeatures v0–v2 are flexible from v0 (compact strings/arrays plus tagged fields; request header 2, response header 1). v0 AllowDowngrade. v1 UpgradeType / ValidateOnly. v2 omits Results. ThrottleTimeMs is JSON `0+` (`UpdateFeaturesResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` / `::error` / `create_with_errors` fill `0`). Empty-Results v0 and v1 bodies match; v2 omits Results. `update_features_timeout` / `update_features_with_timeout` are Java `UpdateFeaturesOptions.timeoutMs` (RPC deadline and TimeoutMs). Kafka 4.0 `validVersions` is `0-2`. This crate speaks 0–2. v3+ is not spoken.
- AlterUserScramCredentials v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_alter_user_scram_credentials_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Results only one version. There is no top-level ErrorCode. Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- DescribeUserScramCredentials v0 is flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`DescribeUserScramCredentialsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` / `::error` fill `0`). Empty-Results only one version. Top-level ErrorCode is at bytes 4–5. Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- AlterClientQuotas v0 is classic. v1 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_alter_client_quotas_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Entries v0 and v1 bodies differ. There is no top-level ErrorCode. Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ is not spoken.
- DescribeClientQuotas v0 is classic. v1 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`DescribeClientQuotasResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` / `::error` fill `0`). Empty-Entries v0 and v1 bodies differ. Top-level ErrorCode is at bytes 4–5. `DescribeClientQuotasResponse.error_counts` is Java `DescribeClientQuotasResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; quota entries are not counted; not `DescribeClientQuotasResponse.error` / AlterClientQuotas `errorCounts`). Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ is not spoken.
- IncrementalAlterConfigs v0 is classic. v1 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_incremental_alter_configs_resource_results_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Responses v0 and v1 bodies differ. `incremental_alter_configs_for` is Java `incrementalAlterConfigs(Map)` (Resources of N). `AlterConfig::append` / `AlterConfig::subtract` are Java `AlterConfigOp.OpType.APPEND` / `SUBTRACT` (LIST configs; op 2 / 3). `incremental_alter_configs_timeout` is Java `AlterConfigsOptions.timeoutMs` (RPC deadline; IncrementalAlterConfigs has no TimeoutMs). Kafka 4.0 `validVersions` is `0-1`. This crate speaks 0–1. v2+ is not spoken.
- AlterReplicaLogDirs v1 is classic. v2 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). Same fields. ThrottleTimeMs is JSON `0+` (`AlterReplicaLogDirsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `AlterReplicaLogDirsResponse::new` fills `0`). `alter_replica_log_dirs_timeout` is Java `AlterReplicaLogDirsOptions.timeoutMs` (RPC deadline; AlterReplicaLogDirs has no TimeoutMs). Kafka 4.0 `validVersions` is `1-2` (v0 removed). This crate speaks 1–2. v0 and v3+ are not spoken.
- DescribeLogDirs v1 is classic. v2–v4 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v3 top-level ErrorCode (KIP-784). v4 TotalBytes / UsableBytes (KIP-827; decode fills `-1` on v1–v3). ThrottleTimeMs is JSON `0+` (`DescribeLogDirsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `DescribeLogDirsResponse::new` fills `0`). `DescribeLogDirsRequest.error_response` is Java `DescribeLogDirsRequest.getErrorResponse` (empty Results; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request Topics are not copied; encode omits ErrorCode below v3). `describe_log_dirs_timeout` / `describe_replica_log_dirs_timeout` / `describe_broker_log_dirs_timeout` are Java `DescribeLogDirsOptions.timeoutMs` (RPC deadline; DescribeLogDirs has no TimeoutMs). Kafka 4.0 `validVersions` is `1-4` (v0 removed). This crate speaks 1–4. v0 and v5+ are not spoken. v5 is a named STATUS hole.
- CreateDelegationToken v1 is classic. v2–v3 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v3 OwnerPrincipalType / OwnerPrincipalName and TokenRequesterPrincipalType / TokenRequesterPrincipalName (decode fills `None` / empty on v1–v2). ErrorCode is the first field (bytes 0–1); ThrottleTimeMs is last (JSON `0+`; `CreateDelegationTokenResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `CreateDelegationTokenResponse::new` fills `0`). `CreateDelegationTokenResponse.error_counts` is Java `CreateDelegationTokenResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; token fields are not counted). `CreateDelegationTokenResponse.has_error` is Java `CreateDelegationTokenResponse.hasError` (`error() != NONE`; Java `error()` is `Errors.forCode` only; crate `error()` stays `getErrorResponse`). `create_delegation_token_timeout` is Java `CreateDelegationTokenOptions.timeoutMs` (RPC deadline; CreateDelegationToken has no TimeoutMs). Kafka 4.0 `validVersions` is `1-3` (v0 removed). This crate speaks 1–3. v0 and v4+ are not spoken. Broker-only (`LeastLoadedNodeProvider`); broker-side `forwardToController` is not a client 41 hop.
- RenewDelegationToken v1 is classic. v2 is flexible (compact bytes plus tagged fields; request header 2, response header 1). Same fields. ErrorCode is the first field (bytes 0–1); ThrottleTimeMs is last (JSON `0+`; `RenewDelegationTokenResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `RenewDelegationTokenResponse::new` fills `0`). `RenewDelegationTokenResponse.error_counts` is Java `RenewDelegationTokenResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; expiry is not counted). `RenewDelegationTokenResponse.has_error` is Java `RenewDelegationTokenResponse.hasError` (`error() != NONE`; Java `error()` is `Errors.forCode` only). `RenewDelegationTokenRequest.error_response` is Java `RenewDelegationTokenRequest.getErrorResponse` (ExpiryTimestampMs JSON default `0`; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request HMAC / RenewPeriodMs are not copied). `renew_delegation_token_timeout` is Java `RenewDelegationTokenOptions.timeoutMs` (RPC deadline; RenewDelegationToken has no TimeoutMs). Kafka 4.0 `validVersions` is `1-2` (v0 removed). This crate speaks 1–2. v0 and v3+ are not spoken. Broker-only (`LeastLoadedNodeProvider`); broker-side `forwardToController` is not a client 41 hop.
- ExpireDelegationToken v1 is classic. v2 is flexible (compact bytes plus tagged fields; request header 2, response header 1). Same fields. ErrorCode is the first field (bytes 0–1); ThrottleTimeMs is last (JSON `0+`; `ExpireDelegationTokenResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `ExpireDelegationTokenResponse::new` fills `0`). `ExpireDelegationTokenResponse.error_counts` is Java `ExpireDelegationTokenResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; expiry is not counted). `ExpireDelegationTokenResponse.has_error` is Java `ExpireDelegationTokenResponse.hasError` (`error() != NONE`; Java `error()` is `Errors.forCode` only). `ExpireDelegationTokenRequest.error_response` is Java `ExpireDelegationTokenRequest.getErrorResponse` (ExpiryTimestampMs JSON default `0`; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request HMAC / ExpiryTimePeriodMs are not copied). `expire_delegation_token_timeout` is Java `ExpireDelegationTokenOptions.timeoutMs` (RPC deadline; ExpireDelegationToken has no TimeoutMs). Kafka 4.0 `validVersions` is `1-2` (v0 removed). This crate speaks 1–2. v0 and v3+ are not spoken. Broker-only (`LeastLoadedNodeProvider`); broker-side `forwardToController` is not a client 41 hop.
- DescribeDelegationToken v1 is classic. v2–v3 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). Request Owners is the same on v1–v3. v3 TokenRequesterPrincipalType / TokenRequesterPrincipalName on each token (decode fills empty on v1–v2). ErrorCode is the first field (bytes 0–1); ThrottleTimeMs is last (JSON `0+`; `DescribeDelegationTokenResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `DescribeDelegationTokenResponse::new` fills `0`). Empty-Tokens v2 and v3 bodies match. `DescribeDelegationTokenResponse.error_counts` is Java `DescribeDelegationTokenResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; tokens are not counted). `DescribeDelegationTokenRequest.error_response` is Java `DescribeDelegationTokenRequest.getErrorResponse` (empty Tokens; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request Owners are not copied). `DescribeDelegationTokenResponse.has_error` is Java `DescribeDelegationTokenResponse.hasError` (`error() != NONE`; Java `error()` is `Errors.forCode` only). `describe_delegation_token_timeout` is Java `DescribeDelegationTokenOptions.timeoutMs` (RPC deadline; DescribeDelegationToken has no TimeoutMs). Kafka 4.0 `validVersions` is `1-3` (v0 removed). This crate speaks 1–3. v0 and v4+ are not spoken. Broker-only (`LeastLoadedNodeProvider`); the handler does not `forwardToController`.
- DescribeGroups v0–v4 are classic. v5–v6 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). v1 ThrottleTimeMs. v3 IncludeAuthorizedOperations / AuthorizedOperations. v4 GroupInstanceId. v6 ErrorMessage and GROUP_ID_NOT_FOUND (KIP-1043). `describe_groups` / `describe_classic_groups` send FindCoordinator v4+ CoordinatorKeys of N (KIP-699) and one DescribeGroups RPC per coordinator. `describe_consumer_groups` tries ConsumerGroupDescribe first and uses DescribeGroups for classic groups (same FindCoordinator hop). `describe_groups_timeout` / `describe_classic_groups_timeout` / `describe_consumer_groups_timeout` are Java `DescribeClassicGroupsOptions` / `DescribeConsumerGroupsOptions.timeoutMs` (RPC deadline; neither RPC has TimeoutMs). Kafka 4.0 `validVersions` is `0-6`. This crate speaks 0–6. v7+ is not spoken.
- ListGroups v0–v2 are classic. v3–v5 are flexible (compact strings/arrays plus tagged fields; request header 2, response header 1). v1 ThrottleTimeMs. v4 StatesFilter / GroupState (KIP-518). v5 TypesFilter / GroupType (KIP-848). `ListGroupsResponse.error_counts` is Java `ListGroupsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; listed groups are not counted). `list_groups_timeout` / `list_consumer_groups_timeout` are Java `ListGroupsOptions` / `ListConsumerGroupsOptions.timeoutMs` (RPC deadline; ListGroups has no TimeoutMs). Kafka 4.0 `validVersions` is `0-5`. This crate speaks 0–5. v6+ is not spoken.
- DeleteGroups v0–v1 are classic. v2 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`encode_delete_groups_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Results v0 and v1 bodies match; v2 is compact. There is no top-level ErrorCode. `DeleteGroupsRequest.error_response` is Java `DeleteGroupsRequest.getErrorResponse` (copies group ids through `error_result_collection`; ThrottleTimeMs JSON `0+`; convenience encode still writes `0`; official Java sets `throttleTimeMs` from the argument). Kafka 4.0 `validVersions` is `0-2`. This crate speaks 0–2. v3+ is not spoken.
- AlterConfigs (legacy api 33) v0–v1 are classic. ThrottleTimeMs is JSON `0+` (on the wire for v0–v2). `shouldClientThrottle` is v1+ (KIP-219). v2 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). `alter_configs_for` is Java `alterConfigs(Map)` (Resources of N). `alter_configs_timeout` is Java `AlterConfigsOptions.timeoutMs` (RPC deadline; AlterConfigs has no TimeoutMs). Kafka 4.0 `validVersions` is `0-2`. This crate speaks 0–2. v3+ is not spoken.
- DeleteRecords v0–v1 are classic. ThrottleTimeMs is JSON `0+` (on the wire for v0–v2). `shouldClientThrottle` is v1+ (KIP-219). v2 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). `delete_records_for` sends one Topics/Partitions array per leader (Java `deleteRecords(Map)` of `RecordsToDelete`). `delete_records_timeout` / `delete_records_for_timeout` are Java `DeleteRecordsOptions.timeoutMs` (RPC deadline and TimeoutMs). Kafka 4.0 `validVersions` is `0-2`. This crate speaks 0–2. v3+ is not spoken.
- DescribeProducers v0 is flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`DescribeProducersResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` fills `0`). Empty-topics only one version. There is no top-level ErrorCode. `describe_producers_for` sends one Topics/PartitionIndexes array per leader (Java `describeProducers(Collection)`). `describe_producers_for_on_broker` is Java `DescribeProducersOptions.brokerId` (one RPC to that broker; `NOT_LEADER_OR_FOLLOWER` is not retried on the Metadata leader). `describe_producers_timeout` / `describe_producers_for_timeout` are Java `DescribeProducersOptions.timeoutMs` (RPC deadline; DescribeProducers has no TimeoutMs). Kafka 4.0 `validVersions` is `0`. This crate speaks 0. v1+ is not spoken. Partition-leader hop only (`NOT_LEADER_OR_FOLLOWER`) unless `brokerId` is set; not a controller hop and not a transaction-coordinator hop.
- AllocateProducerIds v0 is flexible (compact tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`AllocateProducerIdsResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` fills `0`). Top-level ErrorCode is at bytes 4–5. `AllocateProducerIdsResponse.error_counts` is Java `AllocateProducerIdsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; ProducerIdStart / ProducerIdLen are not counted). `AllocateProducerIdsRequest.error_response` is Java `AllocateProducerIdsRequest.getErrorResponse` (ProducerIdStart / ProducerIdLen JSON default `0`; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request BrokerId / BrokerEpoch are not copied). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- UnregisterBroker v0 is flexible (compact strings plus tagged fields; request header 2, response header 1). ThrottleTimeMs is JSON `0+` (`UnregisterBrokerResponse.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` fills `0`). Empty-ErrorMessage only one version. Top-level ErrorCode is at bytes 4–5. `UnregisterBrokerRequest.error_response` is Java `UnregisterBrokerRequest.getErrorResponse` (ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java sets `throttleTimeMs` from the argument; request BrokerId is not copied). Kafka 4.0 `validVersions` is `"0"`. This crate speaks 0. v1+ is not spoken.
- DescribeCluster v0–v2 are flexible from v0 (compact strings/arrays plus tagged fields; request header 2, response header 1). v1 EndpointType. v2 IsFenced. ThrottleTimeMs is JSON `0+` (`ClusterDescription.throttle_time_ms`; encode previously always wrote `0` and decode discarded; `::new` fills `0`). Empty-Brokers v0 and v1 bodies differ; v1 and v2 bodies match. Top-level ErrorCode is at bytes 4–5. `DescribeClusterResponse.error_counts` is Java `DescribeClusterResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; brokers are not counted). `DescribeClusterRequest.error_response` is Java `DescribeClusterRequest.getErrorResponse` (empty Brokers; ClusterId / ControllerId / EndpointType / ClusterAuthorizedOperations JSON defaults; ErrorMessage JSON-null; ThrottleTimeMs JSON `0+`; convenience fills `0`; official Java does not set `throttleTimeMs` from the argument; request IncludeClusterAuthorizedOperations / EndpointType / IncludeFencedBrokers are not copied). Kafka 4.0 `validVersions` is `0-2`. This crate speaks 0–2. v3+ is not spoken.
- CreateAcls / DescribeAcls / DeleteAcls v0–v1 are classic. v1 adds ResourcePatternType / PatternTypeFilter (LITERAL on create; ANY on describe/delete filters). v2–v3 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v3 is the same layout (user resource type). CreateAcls ThrottleTimeMs is JSON `0+` (`encode_create_acls_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). Empty-Results v0 and v1 bodies match; v2 and v3 bodies match. There is no top-level ErrorCode. DescribeAcls ThrottleTimeMs is JSON `0+` (`encode_describe_acls_response_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). DescribeAcls ErrorCode is JSON `0+` (`encode_describe_acls_response_with_error_code`; encode previously always wrote `0` and decode read it without returning it; convenience encode still writes `0`; not CreateAcls result ErrorCode / DeleteAcls filter ErrorCode / DeleteAcls matching ErrorCode / Metadata ErrorCode). `DescribeAclsResponse.error_counts` is Java `DescribeAclsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`). `DescribeAclsResponse.error` is Java `DescribeAclsResponse.error` (`ApiError` from top-level ErrorCode and ErrorMessage). DescribeAcls ErrorMessage is JSON `0+` (nullable STRING; `encode_describe_acls_response_with_error_message`; encode previously always wrote null and decode discarded; convenience encode still writes null; not CreateAcls result ErrorMessage / DeleteAcls filter ErrorMessage / DeleteAcls matching ErrorMessage). Empty-Resources v0 and v1 bodies match; v2 and v3 bodies match. Top-level ErrorCode is at bytes 4–5. DeleteAcls ThrottleTimeMs is JSON `0+` (`encode_delete_acls_filter_results_with_throttle`; encode previously always wrote `0` and decode discarded; convenience encode still writes `0`). DeleteAcls matching ErrorMessage is JSON `0+` (nullable STRING on each MatchingAcl; `DeletedAclsFilterResult.matching` is `DeleteAclsMatchingAcl`; encode previously always wrote null via `ApiError::NONE` and decode discarded; `encode_delete_acls_response` still writes null; not DescribeAcls ErrorMessage / CreateAcls result ErrorMessage / DeleteAcls filter ErrorMessage). Empty-FilterResults v0 and v1 bodies match; v2 and v3 bodies match. There is no top-level ErrorCode. `describe_acls_with` is Java `describeAcls(AclBindingFilter)` (null name/principal/host; Operation and Permission ANY). `describe_acls_any` is Java `describeAcls(AclBindingFilter.ANY)`. `delete_acls_with` is Java `deleteAcls(Collection)` (Filters of N). `create_acls_timeout` / `describe_acls_timeout` / `delete_acls_timeout` are Java `CreateAclsOptions` / `DescribeAclsOptions` / `DeleteAclsOptions.timeoutMs` (RPC deadline; these RPCs have no TimeoutMs). Kafka 4.0 `validVersions` is `1-3` (v0 removed). This crate speaks 0–3. v4+ is not spoken.
- Metadata v1–v8 are classic; v9–v13 are flexible (compact arrays/strings plus tagged fields; request header 2, response header 1). v10 TopicId (name-based describes send a zero UUID; `describe_topics_by_id` is Java `describeTopics(TopicCollection.ofTopicIds)` with null Name). v12 supports topicId. ClusterAuthorizedOperations is JSON `8-10` (`MetadataResponse.cluster_authorized_operations`; encode previously always wrote `AUTHORIZED_OPERATIONS_OMITTED` and decode discarded; below v8 and above v10 omit even when non-default and decode fills `AUTHORIZED_OPERATIONS_OMITTED`; `::error_response` fills the JSON default; not TopicAuthorizedOperations / DescribeCluster `cluster_authorized_operations` / IncludeTopicAuthorizedOperations). IncludeClusterAuthorizedOperations is JSON `8-10` (`encode_metadata_request_topics_with_include_cluster_authorized_operations`; encode previously always wrote `false` and decode discarded; below v8 and above v10 omit even when true and decode fills `false`; convenience encode still writes `false`; official Java `MetadataRequestData.includeClusterAuthorizedOperations`; not IncludeTopicAuthorizedOperations / ClusterAuthorizedOperations / DescribeCluster `cluster_authorized_operations`). v13 adds top-level ErrorCode INT16 after Topics (and after ClusterAuthorizedOperations on v8–v10) and before tagged fields. Name-based `describe_topics` / `describe_topics_with` / `describe_topics_with_partition_limit` are Java `describeTopics(TopicCollection.ofTopicNames)` via DescribeTopicPartitions api 75 (KIP-966; `ResponsePartitionLimit` default 2000, `DescribeTopicsOptions.partitionSizeLimitPerResponse`, and `NextCursor` pages); TopicAuthorizedOperations is stored when `describe_topics_with(true)` (DTP has no IncludeTopicAuthorizedOperations request flag). `describe_topic_partitions_timeout` is the crate-first RPC deadline (DescribeTopicPartitions has no TimeoutMs). Java `describeTopics` is `describe_topics_timeout`. Kafka 4.0 `validVersions` is `0-13`. This crate speaks 1–13. v0 (empty array means all topics) and v14+ are not spoken.
- ApiVersions v0–v2 are classic (empty request; v1+ ThrottleTimeMs). v3–v4 are flexible in the body only (compact strings plus tagged fields; request header 2; response header stays 0, KIP-482). v3 adds ClientSoftwareName / ClientSoftwareVersion. v4 is the same request as v3 and allows SupportedFeatures.MinVersion 0 (KAFKA-17011 / KAFKA-17492; v0–v3 omit those features). Tagged fields: 0 `supportedFeatures` (name, min, max), 1 `finalizedFeaturesEpoch` INT64 (`-1` omitted), 2 `finalizedFeatures` (name, **max** then min), 3 `zkMigrationReady`. Empty/default tags are omitted. `ApiVersionsResponse.error_counts` is Java `ApiVersionsResponse.errorCounts` (top-level ErrorCode only, including `NONE`; Java `Collections.singletonMap`; api keys / features are not counted). `describe_features_timeout` is Java `DescribeFeaturesOptions.timeoutMs` (RPC deadline; ApiVersions has no TimeoutMs). Kafka 4.0 `validVersions` is `0-4`. This crate speaks 0–4 and sends v4 on connect. When the broker returns `UNSUPPORTED_VERSION` (KIP-511, brokers 2.4+), the error body is v0 and lists the supported ApiVersions range; the client retries at `pick_version(broker_min, broker_max, 0, 4)`. v5+ is not spoken.
## Compression
gzip uses `flate2` with its Rust backend. snappy uses the `snap` crate
(snappy-java framing on produce, raw snappy accepted on fetch). lz4 uses
`lz4_flex` LZ4 frames (independent 64KiB blocks, proper header checksum for
magic ≥ 1). zstd is not implemented; the Kafka ecosystem codec is typically C
(`zstd-sys`).
## TLS
Set `ProducerConfig.tls` / `ConsumerConfig.tls` to a `TlsConfig`. Handshake is
`rustls` with the `ring` backend (not OpenSSL). Custom CA PEM, or Mozilla
roots if `ca_pem` is omitted. Optional client cert/key for mTLS. SNI defaults
to the bootstrap host. Plain TCP stays a `TcpStream`; TLS is a separate
connection type so the uncompressed hot path does not pay for rustls.
Writes pump reads into the connection buffer. TLS (and a full TCP window)
can otherwise stall `poll_write` until `poll_read` runs, which deadlocks a
pipelined producer that only reads after `max_in_flight` writes.