Consumer Poll Loop
IntermediateThe consumer calls poll() in a loop to heartbeat with the coordinator and fetch records; max.poll.interval.ms must exceed the time to process a batch or the consumer is evicted.
Overview
The Kafka consumer is built around a single-threaded poll loop. The application calls poll(timeout) repeatedly to both heartbeat with the group coordinator and fetch new records. This is not just a data fetch — every call to poll() serves as an implicit heartbeat, proving the consumer is alive. If poll() is not called within max.poll.interval.ms (default 5 minutes), the coordinator declares the consumer dead, triggers a rebalance, and reassigns its partitions to other consumers. Understanding the poll loop and its configuration knobs is essential for writing consumers that are both responsive and stable under load.
The Poll Loop — Anatomy
The poll loop has four responsibilities: 1. **Send heartbeat** — proves the consumer is alive to the group coordinator. 2. **Fetch records** — retrieves a batch of messages from assigned partitions. 3. **Trigger rebalance callbacks** — calls onPartitionsRevoked / onPartitionsAssigned when a rebalance occurs. 4. **Trigger offset commit callbacks** — if commitAsync callback is pending.
The poll(Duration timeout) parameter is NOT how long Kafka will wait for messages to arrive — it is the maximum time the call will block if no records are available. If records are available immediately, poll() returns them right away.
KafkaConsumer<String, Order> consumer = new KafkaConsumer<>(props);
consumer.subscribe(List.of("orders"), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// Commit offsets for revoked partitions before rebalance completes
consumer.commitSync();
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
log.info("Assigned partitions: {}", partitions);
}
});
boolean running = true;
while (running) {
// poll() also sends heartbeat + triggers rebalance callbacks
ConsumerRecords<String, Order> records = consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<String, Order> record : records) {
processOrder(record.value());
}
consumer.commitAsync(); // async offset commit after each batch
}
consumer.commitSync(); // final synchronous commit on shutdown
consumer.close();max.poll.interval.ms — The Most Common Eviction Cause
max.poll.interval.ms (default: 300 000 ms = 5 minutes) is the maximum time between two consecutive poll() calls. If the processing of a single batch takes longer than this, the coordinator declares the consumer dead and triggers a rebalance.
This is the most common cause of unexpected rebalances in production. It happens when: - Processing is slow (heavy DB writes, external API calls) - Batch size (max.poll.records) is too large - The application is paused on a lock or IO wait
Fix: reduce max.poll.records so each batch is smaller and processes faster, OR increase max.poll.interval.ms to match your actual processing time.
# Tuning for slow processing
# Option 1: Reduce batch size — fewer records per poll, faster to process
spring:
kafka:
consumer:
max-poll-records: 50 # default 500; lower for slow processing
properties:
max.poll.interval.ms: 60000 # 60s — must exceed worst-case batch processing time
# Option 2: Increase the timeout to match processing reality
spring:
kafka:
consumer:
properties:
max.poll.interval.ms: 300000 # 5 min (default) — increase if needed
# Monitoring: if you see frequent unexpected rebalances in logs, check:
# 1. How long each batch is taking to process
# 2. Whether max.poll.interval.ms < actual processing time
// Spring Kafka — pause the listener container for heavy processing
// and resume after — keeps the poll loop running while processing offline
@KafkaListener(topics = "orders", id = "orderListener")
public void onOrder(List<Order> orders) {
registry.getListenerContainer("orderListener").pause();
// heavy async processing — poll loop still runs (just pauses fetch)
processAsync(orders)
.thenRun(() -> registry.getListenerContainer("orderListener").resume());
}session.timeout.ms vs max.poll.interval.ms
Two different timeouts control consumer liveness:
**session.timeout.ms** (default 45 000 ms) — how long the coordinator waits for a heartbeat before marking the consumer dead. Heartbeats are sent automatically by a background thread (since Kafka 0.10+) independently of poll(). If the JVM is busy (GC pause, deadlock), the heartbeat thread may not fire in time.
**max.poll.interval.ms** (default 300 000 ms) — how long between poll() calls before the consumer is considered stuck. This catches slow processing even if the JVM is healthy enough to heartbeat.
A consumer can be evicted by either timeout — always set max.poll.interval.ms > the time to process one full batch, and keep session.timeout.ms large enough to survive GC pauses.
spring:
kafka:
consumer:
properties:
# Background heartbeat timeout — increase if you have long GC pauses
session.timeout.ms: 45000 # default; increase on JVM with large heaps
# Poll interval — increase beyond your worst-case batch processing time
max.poll.interval.ms: 120000 # 2 minutes
# Heartbeat interval — should be 1/3 of session.timeout.ms
heartbeat.interval.ms: 15000 # sends heartbeat every 15sKey Points to Remember
- 1poll() serves double duty: fetches records AND sends the consumer heartbeat to the group coordinator.
- 2max.poll.interval.ms is the max time between poll() calls; exceed it and the coordinator evicts the consumer, triggering a rebalance.
- 3Reduce max.poll.records to ensure each batch processes within max.poll.interval.ms.
- 4session.timeout.ms controls the background heartbeat timeout (GC pauses / JVM unresponsiveness); max.poll.interval.ms controls processing timeout.
- 5Always commit offsets in onPartitionsRevoked to avoid re-processing during planned rebalances.
- 6Spring Kafka's @KafkaListener manages the poll loop automatically — configure max-poll-records and max.poll.interval.ms in application.yml.
Interview Questions
Sign in to ask AriaWhat are the two responsibilities of the Kafka poll() method?
A Kafka consumer is being evicted from the group repeatedly. What would you investigate first?
What is the difference between session.timeout.ms and max.poll.interval.ms?
How does reducing max.poll.records help prevent consumer eviction?
How would you handle a Kafka consumer that processes records by making a slow external API call?
Ask Aria about Consumer Poll Loop
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.