Rakshit Agarwal created CAMEL-25021:
---------------------------------------

             Summary: Kafka consumer silently stalls indefinitely when record 
exceeds max.partition.fetch.bytes
                 Key: CAMEL-25021
                 URL: https://issues.apache.org/jira/browse/CAMEL-25021
             Project: Camel
          Issue Type: Bug
          Components: camel-kafka
    Affects Versions: 4.14.5
            Reporter: Rakshit Agarwal
             Fix For: 4.14.5


The camel-kafka consumer enters an infinite silent loop when a Kafka topic 
contains a record larger than the consumer's max.partition.fetch.bytes (default 
1MB). The consumer calls poll() repeatedly, receives empty ConsumerRecords, 
processes zero records, and loops back — with no error, no log, and no 
progress. The consumer appears alive but is permanently stalled.

*Root Cause*

In KafkaFetchRecords.java, the main poll loop (line 376):

  \{code:java}
  while (isKafkaConsumerRunnableAndNotStopped() && isConnected() && 
pollExceptionStrategy.canContinue()) {
      ConsumerRecords<Object, Object> allRecords = consumer.poll(pollDuration);
      ProcessingResult result = 
recordProcessorFacade.processPolledRecords(allRecords);
      updateTaskState();
      if (result != null && result.isBreakOnErrorHit() && 
!this.state.get().equals(State.PAUSED)) {
          setReconnect(true);
          setConnected(false);
      }
  }
  \{code}

When the Kafka broker encounters a record larger than 
max.partition.fetch.bytes, it returns an empty fetch response — this is correct 
Kafka protocol behavior, the broker cannot fit the record into the fetch 
buffer. The Camel consumer receives empty ConsumerRecords, processPolledRecords 
processes zero records without error, and the while loop continues.

There is no detection for the "stuck on same offset with repeated empty polls" 
condition. The pollExceptionStrategy is never triggered because no exception 
occurs — the poll succeeds, it just returns nothing. No code path in 
KafkaFetchRecords tracks whether the consumer offset is advancing or stalled.

The KafkaConfiguration class exposes maxPartitionFetchBytes as a configurable 
property, but does not set a default — inheriting the Kafka client's default of 
1,048,576 bytes (1MB). This means any record >1MB published to the topic by any 
producer becomes a permanent poison pill that silently stalls the consumer.

On our side, we have added a 5MB MessageSizeLimitProcessor at the application 
level to reject oversized messages after they are consumed. However, this 
processor is never reached for messages >1MB because the consumer stalls before 
fetching them — making the application-level size check unreachable.

The only current workaround is to increase maxPartitionFetchBytes to a value 
larger than the largest possible message (e.g., 50MB). This trades the silent 
stall for excessive memory
consumption — the entire oversized record is fetched into consumer heap, 
deserialized into a Java String, and only then rejected by application-level 
checks downstream. A 50MB message consumes ~100MB of heap (byte[] + String) 
before rejection.

*Steps to Reproduce*

  # Create a Kafka topic with max.message.bytes set to allow large messages 
(e.g., 10MB)
  # Produce a message larger than 1MB (e.g., 6MB) to the topic
  # Produce several normal-sized messages after the oversized one
  # Start a camel-kafka consumer route with default configuration (no 
maxPartitionFetchBytes override)
  # Observe: the consumer processes messages up to the offset before the 
oversized record, then stalls indefinitely
  # Check consumer group: CURRENT-OFFSET stops advancing. Check logs: no error, 
no warning at any level

*Expected Behavior*

The consumer should detect that it is stuck at the same offset receiving 
consecutive empty polls and surface an error — either by throwing an exception 
routable to the configured errorHandler, or by logging a WARN that clearly 
identifies the stuck offset, partition, and max.partition.fetch.bytes value. 
The consumer should not silently loop forever with no indication of the problem.

*Actual Behavior*

The consumer loops indefinitely with no error, no warning, and no progress. The 
poll loop has no tracking of whether the offset is advancing. The 
pollExceptionStrategy is never invoked because no exception occurs. TRACE-level 
"Polling..." messages continue but provide no indication of the stall.

*Impact / Severity*

  * Any single oversized record in any partition permanently stalls the 
consumer for that partition with no log output
  * Restarting the consumer does not recover — it resumes from the same stuck 
offset and stalls again
  * The consumer thread remains alive and the consumer group shows the member 
as active — standard monitoring (thread health, consumer group lag alerts) may 
not detect the stall because lag appears constant rather than growing
  * No exception is surfaced to the Camel route, error handler, or application



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to