Kafka Share Groups Change Acknowledgement, Not Business Atomicity
- Published on
- /8 mins read

Consumer A acquires record 10 and calls an external provider. The effect finishes. Before A commits the acknowledgement, the acquisition lock expires. Consumer B receives record 10 and calls the provider again.
There is no traditional committed-offset hole to fix. A share group uses per-record acquisition and acknowledgement state, not exclusive partition ownership plus one committed next-offset frontier.
Kafka 4.3.1 makes share groups production-ready at the Apache release level. That does not make every managed service, client, sink, or business effect share the same guarantee. The 4.3 upgrade notes and KafkaShareConsumer 4.3.1 source are the baseline here.
More consumers than partitions changes the ownership model
Traditional consumer groups assign a partition to one member at a time. Share groups can let several members acquire different records from one partition. That allows record-level cooperative work and gives up the traditional partition-owner ordering assumption.
| Requirement | Traditional group | Share group |
|---|---|---|
| Work distribution | One member owns a partition at a time | Several members can acquire records from one partition |
| Progress representation | Coordinator-stored next-offset boundary | Per-record acquisition and acknowledgement state |
| Ordering | Partition order is the base model | Returned records are ordered in a batch, but processing across members can reorder |
| Failure recovery | Reassign partition and resume from stored offset | Acquisition expires/releases and record can become available again |
| External effect protection | Application/provider contract | Application/provider contract |
A share group can fit queue-like work where record-level distribution matters more than partition-owner order. It does not turn a Kafka topic into a workflow system with universal scheduling, cancellation, priority, compensation, or provider transactions.
Acquisition locks are temporary authority
When poll() returns a record, Kafka acquires it for one share consumer under a time-limited acquisition lock. The current 4.3.1 Java API describes four acknowledgement types in AcknowledgeType:
| Type | Record-state intent | What it does not establish |
|---|---|---|
ACCEPT | Processing succeeded | Business effect atomicity before server acknowledgement |
RELEASE | Make the record eligible for another delivery attempt | Failure recovery or preserved order |
REJECT | Do not release for another normal attempt; record becomes archived under the share model | DLQ publication, deletion from other groups, or business completion |
RENEW | Extend acquisition while processing continues | Permanent authority or successful effect |
The current group setting is share.record.lock.duration.ms. Some KafkaShareConsumer prose still shows the older group.share.record.lock.duration.ms spelling. The generated/current GroupConfig definition is the source used by this guide.
Lock expiry makes an unacknowledged record available again. It cannot cancel a request already accepted by a database or HTTP provider. Use an operation key, conditional mutation, or downstream fence when the effect must reject a second owner.
Local acknowledgement and committed acknowledgement are separate
In explicit acknowledgement mode, the application calls:
consumer.acknowledge(record, AcknowledgeType.ACCEPT);That call updates local client information. The KafkaShareConsumer contract states that commitSync(), commitAsync(), or a later valid poll() commits pending acknowledgement state to Kafka. A returned local method call is not by itself a durable server acknowledgement.
Explicit mode requires an acknowledgement type for every record returned by the prior poll() before the next poll(). Otherwise, Java throws IllegalStateException; the unacknowledged records can still be acknowledged. This constraint makes a traditional “poll a batch, hand every record to an unbounded executor, poll again” recipe invalid without an explicit acknowledgement/renewal design.
In implicit acknowledgement mode, the next poll() or an explicit commit can acknowledge the previous delivered batch as successfully processed. That is a strong coupling between poll lifecycle and application completion. If the application hands work to background handlers and polls again too early, it can acknowledge records that have not completed under its business contract.
For both modes, a lost commit response creates an observer problem. Server state and client knowledge can differ. Do not translate that uncertainty into a traditional committed offset or assume a second effect-capable attempt is free.
Renewal keeps the poll loop in the design
Kafka 4.3.1 includes AcknowledgeType.RENEW. The KIP-1222 design and current Java API require explicit mode and continued polling. The application acknowledges the record with RENEW on each renewal iteration; when renewal succeeds, the record returns through poll() again.
The current group setting share.renew.acknowledge.enable can permit or deny renewal. A renewal response can fail or disappear. Even a successful renewal extends one bounded lock; it does not guarantee that the member will hold the record forever or that the external effect remains cancellable.
Keep three times separate:
- acquisition expiry;
- poll liveness deadline;
- business-operation deadline.
Extending the first does not reset the third.
Share isolation is group-wide in the current definition
Traditional KafkaConsumer uses the client setting isolation.level. KafkaShareConsumer explicitly rejects that traditional setting in ShareConsumerConfig.
The current group setting is share.isolation.level, with read_uncommitted as the documented default and read_committed supported. Again, older class prose can show group.share.isolation.level; this guide keeps the current generated definition for configuration work and the older literal only so readers can recognize the source discrepancy.
Under read_committed, eligible share records are bounded by the partition's last stable offset. An open transaction can block later records. Under read_uncommitted, delivery is bounded by the high watermark and can include aborted transactional data. The same exclusive LSO/HW rules from the transaction protocol article still matter; the acquisition state above them is different.
Delivery count and rejection do not create a DLQ
The group setting share.delivery.count.limit bounds the current share delivery-count policy. Reaching a limit or rejecting a record changes its share-group state. It does not:
- publish a record to a dead-letter topic;
- preserve the original payload after every retention event;
- record a successful business outcome;
- remove the record from every other group;
- authorize later redrive.
If you need a DLQ, define publication/provenance/progress consistency and redrive identity separately. The Kafka recovery planner models those boundaries for traditional transactional offsets. Do not apply sendOffsetsToTransaction() to share acknowledgements or claim an atomic share-ack-plus-output transaction without a separately verified API.
The six advanced cases have their own reducer and evidence gate
The Kafka Reliability Field Guide includes these source-reviewed scenarios:
- SG01: two consumers acquire different records from one partition;
- SG02: effect completes, lock expires, another consumer repeats it;
- SG03: local accept, committed server acknowledgement, and lost response;
- SG04: release, reject/archive, and delivery-limit exhaustion;
- SG05: renewal permitted, denied, or response lost;
- SG06: group-wide committed isolation and an open transaction.
They use lib/kafka-reliability/share-acquisition.ts, not the traditional completed-offset frontier.
Interactive release has a stronger gate: fixture F08 must compile and run against pinned Apache Kafka 4.3.1 and the Java 4.3.1 share client, covering explicit/implicit acknowledgement, renewal, rejection, delivery count, same-partition distribution, and group isolation. The isolated 2026-09-08 evidence summary records F08 as passed, so the consumer lab exposes the SG selectors. The same manifest records InvalidRecordStateException for RENEW when share.renew.acknowledge.enable=false. It also captured successful ShareAcknowledge responses for ACCEPT and RENEW, dropped each correlated reply, and recorded the client-side UnknownServerException. The accepted record did not return to another member after the first consumer closed. That exception class is one observed Java 4.3.1 path, not a universal mapping for every retry or broker schedule. Managed-service support, correlated failure, acknowledgement-plus-output transactions, and external-effect atomicity remain unestablished.
Deterministic teaching model · apache-java-4.3.1
SG02: An expired acquisition can repeat an external effect
What happens without a committed acknowledgement?
Current outcome · step 0 of 5
Each record begins available with its own acquisition and acknowledgement state; no committed prefix exists.
Default synthetic example loaded.
Record acquisition
- Partition 0, offset 10
- available
- delivery 0
- Partition 0, offset 11
- available
- delivery 0
- Partition 0, offset 12
- available
- delivery 0
Acknowledgement
- Mode / isolation
- explicit / read_uncommitted
- Server response
- none
- Caller knowledge
- UNKNOWN
- Explicit poll violations
- 0
Business effects
- Offset 10
- 0 effect(s)
- Effects are not reversed by lock expiry, release, reject, or acknowledgement loss.
- Offset 11
- 0 effect(s)
- Effects are not reversed by lock expiry, release, reject, or acknowledgement loss.
- Offset 12
- 0 effect(s)
- Effects are not reversed by lock expiry, release, reject, or acknowledgement loss.
Invariant results
Share mode does not manufacture a committed-prefix frontier
holds in model
Each record carries its own acquisition and acknowledgement state.
Explicit mode acknowledges each returned record before the next poll
holds in model
No explicit-mode poll crossed an unacknowledged acquired record.
Reject/archive does not imply DLQ publication
holds in model
The model changes share record state only; no quarantine topic is created.
Share acknowledgement alone protects one business effect
violated in model
Acquisition expiry permitted another effect-capable attempt.
Ordered trace
The state table shows model truth. Each observer result says what the current caller can establish at that step.
| Step | Actor | Action | Observer result |
|---|---|---|---|
| 10 ms | share consumer | Consumer A acquires offset 11 | consumer-a acquires record 11 until virtual time 30000. |
| 21,000 ms | external provider | Consumer A applies an unprotected effect | The unprotected external effect completes. Acquisition expiry or acknowledgement failure cannot roll it back. |
| 331,000 ms | clock | Advance beyond acquisition expiry | Virtual time advances without expiring a current acquisition. |
| 431,001 ms | share consumer | Consumer B reacquires offset 11 | consumer-b acquires record 11 until virtual time 61001. |
| 532,000 ms | external provider | Consumer B applies the effect again | The unprotected external effect completes. Acquisition expiry or acknowledgement failure cannot roll it back. |
Evidence and limits
Assumptions and known limits
- KafkaShareConsumer 4.3.1 semantics are separate from traditional committed-prefix arithmetic.
- F08 gates interactive share behavior, including response-loss variants.
- No traditional offset frontier, share-output transaction claim, or managed-service parity.
The gate matters because KIPs record design history while method behavior, current config names, and supported wire paths can change. The accepted KIP-932 does not replace the 4.3.1 API, source, and fixture.
What could go wrong
- Traditional offset arithmetic enters the share wrapper. One committed next offset cannot represent independent acquired records.
acknowledge()is treated as server commit. The local client update is mistaken for durable acknowledgement.- An explicit-mode consumer polls before every record has an acknowledgement type. The Java API rejects the poll while background handlers still run.
- Implicit mode polls while async handlers remain unfinished. The previous batch can be acknowledged too early for the application contract.
- Renewal is treated as a lease forever. Group policy, poll liveness, and response uncertainty still bound authority.
REJECTis labelled “sent to DLQ.” No quarantine publication happened.- The old
group.share.isolation.levelprose wins over the current definition. Configuration and source evidence drift apart. - Share delivery is called exactly-once processing. Lock expiry and acknowledgement loss can repeat attempts; external effects need another identity contract.
Share groups move the Kafka ownership boundary from partitions toward records. They do not move your provider inside Kafka.
Sources and references
Share this post
Follow future work
Follow public article updates through RSS. Intentionally unlisted posts stay out of the feed.
Working through a similar reliability boundary?
The Async Reliability Review turns one messaging or background-job flow into an evidence map, recovery plan, and owned next actions.

