Kafka troubleshooting
OffsetOutOfRangeException (OFFSET_OUT_OF_RANGE)
org.
The consumer asked for an offset the partition no longer has, almost always because retention deleted it while the consumer was behind or offline. With a reset policy the consumer jumps to the earliest or latest offset and the deleted records are skipped; only with auto.offset.reset=none does poll() throw OffsetOutOfRangeException, "Fetch position FetchPosition{offset=0, ...} is out of range for partition orders-0".
Reproduce it in the playground3 steps beforeretention.ms armed · group offline. t+0.000s
A delete topic with retention.ms=1500. The consumer in group c1 has crashed (its member is offline) so it stays at offset 0. A retention_sweep loop runs every 500 ms (the broker's log.retention.check.interval.ms, 5 minutes by default). Press play to produce.
Open at this step: step 1, retention.ms armed · group offline2 steps beforeTwo batches produced - different ages. t+1.400s
Four records produced (HW=4). Offsets 0-1 were appended at t=200; offsets 2-3 at t=1300. Each record carries its append timestamp. Nothing has aged out yet (all within the 1500 ms window), so logStartOffset is still 0.
Open at this step: step 2, Two batches produced - different ages1 step beforeSweep deleted the aged prefix. t+2.050s
At t=2000 the sweep found offsets 0-1 (age 1800 ms) older than retention.ms=1500 and advanced logStartOffset to 2 (retention_run) - they are gone, while 2-3 survive. Real Kafka deletes whole segments once their newest record is older than retention.ms, so this split assumes 0-1 sit in their own rolled segment. The consumer just restarted, but its position 0 is now below the log start.
Open at this step: step 3, Sweep deleted the aged prefixThe moment it happensOFFSET_OUT_OF_RANGE · reset to earliest. t+2.300s
On its next poll the group fetched from offset 0, but the log now starts at 2. auto.offset.reset=earliest seeks it to logStartOffset (2), it reads the surviving 2-3 and advances to HW=4. A lagging consumer recovered by jumping past the deleted prefix.
Open at this step: step 4, OFFSET_OUT_OF_RANGE · reset to earliestRight afterThe rest ages out too. t+3.200s
By t=3000 offsets 2-3 also crossed the retention.ms window, so the next sweep advanced logStartOffset to 4 - the log is now empty. The consumer already committed offset 4, so it stays in range. The sweep loop keeps running, deleting nothing until new records arrive.
Open at this step: step 5, The rest ages out tooWhat it means
A partition holds offsets from its log start offset to its log end. Retention (retention.ms, retention.bytes) deletes whole old segments and moves the log start forward. A fetch from an offset outside that range gets OFFSET_OUT_OF_RANGE (error code 1), and the consumer applies auto.offset.reset: earliest seeks to the log start, latest (the default) to the log end, by_duration (for example by_duration:P1D) to the offset that far back in time. It logs "Fetch position FetchPosition{offset=0, ...} is out of range for partition orders-0, resetting offset", and once it has looked up the new position, "Resetting offset for partition orders-0 to position FetchPosition{offset=1200, ...}." With none it logs "raising error to the application since no reset policy is configured" and poll() throws org.apache.kafka.clients.consumer.OffsetOutOfRangeException; the protocol error itself maps to org.apache.kafka.common.errors.OffsetOutOfRangeException. When retention caused it, the deleted records are gone for good: the group never processed them. A group with no committed offset at all, for example after offsets.retention.minutes removed it, is a different case: with none it fails with NoOffsetForPartitionException instead.
Likely causes, most common first
- The consumer fell behind retention. Lag grew past retention.ms (7 days by default) or retention.bytes: a slow consumer, a backlog after an outage, or retention lowered to save disk. Retention removes a segment file at a time, so the cut-off moves in steps rather than record by record.
- The group was offline longer than retention. The committed offset stays where the group stopped. When the group returns after the segments holding that offset were deleted, its first fetch is out of range. Scenarios 05.1.1 (reset to earliest) and 05.1.3 (auto.offset.reset=none, group stuck) show both outcomes.
- Total retention passed on a tiered topic. With tiered storage, a segment is copied to the remote tier once it rolls, and local.retention.ms only deletes the local copy, so a lagging consumer can still read it from the remote tier. retention.ms and retention.bytes delete from the remote tier too, and only that deletion makes an old offset out of range, as scenario 05.3.3 shows.
- The application seeks to an offset that does not exist. consumer.seek() to a hard-coded value, to an offset stored outside Kafka, or to one taken from another topic or cluster asks for a position the partition never had or no longer has.
How to fix it
- Measure how far behind the group is before it falls off the log: kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group prints CURRENT-OFFSET, LOG-END-OFFSET and LAG per partition, and the consumer metric records-lag-max tracks it continuously.
- Compare the group's position with the log start: kafka-get-offsets.sh --bootstrap-server localhost:9092 --topic orders --time earliest prints each partition's earliest offset.
- Choose auto.offset.reset on purpose. latest, the default, skips every record still in the log, not only the deleted ones; earliest re-reads everything that survived; none stops the consumer so a person decides.
- To move a stuck group, stop its consumers, then run kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group my-group --topic orders --to-earliest (or --to-datetime, --by-duration) first with --dry-run and then with --execute.
- Give the topic a retention that covers the longest outage you plan for plus the time to catch up, and alert on lag before it reaches that window.
What not to do
- Treating the "resetting offset" log line as noise: each one marks records the group skipped.
- Fixing a stuck auto.offset.reset=none group by switching it to latest without checking what is still in the log.
Does the client retry it?
Not as such. With auto.offset.reset set to earliest, latest or by_duration, the consumer resets its position and fetches again without raising anything. With auto.offset.reset=none, poll() keeps throwing until the application seeks to a valid offset or an operator resets the group's offsets.
Configs involved
auto.Where a consumer starts when its group has no committed offset or the offset no longer exists: latest (the default), earliest, by_duration (a time back from now, such as by_duration:PT1H), or none to throw an exception instead.offset. reset retention.How long a topic with cleanup.policy=delete keeps a log segment before it may be deleted (default 604800000, 7 days); -1 removes the time limit.ms retention.The size a partition may grow to before its oldest segments are deleted under cleanup.policy=delete (default -1, no size limit); the limit is per partition, not per topic.bytes local.On a tiered-storage topic, how long a segment stays on the broker disk once it has been copied to remote storage (default -2, which means retention.ms); the data stays readable from the remote tier until retention.ms removes it.retention. ms offsets.How long the group coordinator keeps the committed offsets of a group with no members, or of a partition the group no longer subscribes to (default 10080, 7 days); after that the group starts again from auto.offset.reset.retention. minutes
Reproduce it in the simulator
- Kafka retention.ms and a consumer that falls behind (Time-based retention (retention.ms)) Open at the moment it happens: Kafka retention.ms and a consumer that falls behind
- Kafka OFFSET_OUT_OF_RANGE with auto.offset.reset=none (Offset out of range) Open at the moment it happens: Kafka OFFSET_OUT_OF_RANGE with auto.offset.reset=none
- Kafka local.retention.ms vs retention.ms in tiered storage (Local vs total retention) Open at the moment it happens: Kafka local.retention.ms vs retention.ms in tiered storage
Sources
- Apache Kafka 4.3: consumer config auto.offset.reset
- Apache Kafka 4.3: topic config retention.ms
- Apache Kafka 4.3: topic config retention.bytes
- Apache Kafka 4.3: broker config offsets.retention.minutes
- Apache Kafka 4.3 Javadoc: org.apache.kafka.clients.consumer.OffsetOutOfRangeException
- Apache Kafka 4.3.0 source: Errors.java (OFFSET_OUT_OF_RANGE, code 1)
- Apache Kafka 4.3.0 source: FetchCollector.java (reset or raise on OFFSET_OUT_OF_RANGE)
- Apache Kafka 4.3: checking consumer position and managing consumer groups
- Apache Kafka 4.3.0 source: GetOffsetShell.java (kafka-get-offsets.sh --time)
- Apache Kafka 4.3: tiered storage
- Apache Kafka 4.3.0 source: SubscriptionState.java (Resetting offset for partition)