🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGorg.springframework.kafka.listener.ListenerExecutionFailedException: Listener method 'public void com.devinhyderabad.kafka.OrderListener.handle(com.devinhyderabad.dto.Order)' threw exception at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.decorateException(KafkaMessageListenerContainer.java:2445) Caused by: org.springframework.kafka.support.serializer.DeserializationException: failed to deserialize

⚡ Quick Fix Works 80% of the time

Add an ErrorHandler (like DefaultErrorHandler in Spring Boot 3) to the listener container factory.

@Bean public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(ConsumerFactory<String, String> cf) { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(cf); factory.setCommonErrorHandler(new DefaultErrorHandler(new DeadLetterPublishingRecoverer(), new FixedBackOff(1000L, 2))); return factory; }

🧠 Why this Happens

Tap to expand the deep technical explanation

Your `@KafkaListener` method threw an unhandled exception, usually due to a deserialization mismatch (e.g., producer sent Avro, consumer expects JSON) or a `NullPointerException` in your business logic. In Spring Kafka, if an exception is thrown, the default behavior is to seek back and retry infinitely, blocking the partition.

The HITEC City Parking Spot Analogy:

A conveyor belt with a broken item. The worker stops the belt, picks up the item, puts it back at the start, and tries again. The belt never moves forward because the item keeps breaking.

🔁 How to Reproduce Confirm this is your error

Create a `@KafkaListener` that throws a `RuntimeException`. Send a message to the topic. The consumer will log this exception repeatedly.

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Configure DefaultErrorHandler (SB3)

👉 Use this to prevent infinite retry loops and move poison pills to a Dead Letter Topic (DLT).

The `DefaultErrorHandler` replaces the old `SeekToCurrentErrorHandler`. It retries a few times, then gives up and sends the message to a DLT.

@Bean public DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> template) { return new DefaultErrorHandler(new DeadLetterPublishingRecoverer(template), new FixedBackOff(1000L, 2)); }
Solution 2

Fix deserialization mismatches

👉 Use this if the root cause is `DeserializationException`.

Ensure the producer and consumer use the exact same serializer/deserializer (e.g., `StringDeserializer` for JSON strings, not `JsonDeserializer` for POJOs unless configured correctly).

spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer spring.kafka.consumer.properties.spring.json.trusted.packages=com.devinhyderabad.dto
Solution 3

Catch the exception inside the listener

👉 Use this if you want to handle specific errors without crashing the container.

Wrap your business logic in a try-catch block so Spring Kafka doesn't see the exception.

@KafkaListener(topics = "orders") public void listen(Order order) { try { process(order); } catch (Exception e) { log.error("Failed to process order", e); // Acknowledge by not throwing } }
Solution 4

Change listener signature to String

👉 Use this to debug raw payloads.

Accept the raw string to inspect what Kafka actually sent before Spring tries to map it.

@KafkaListener(topics = "orders") public void listen(String rawMessage) { log.info("Raw: {}", rawMessage); }
Solution 5

Fix POJO mapping with @Payload

👉 Use this if Spring is failing to map the message to your DTO.

Explicitly tell Spring to treat the message body as a specific type using `@Payload`.

@KafkaListener(topics = "orders") public void listen(@Payload Order order, @Header(KafkaHeaders.RECEIVED_KEY) String key) { ... }

📋 Version Notes

Spring Boot 2.x

Uses `SeekToCurrentErrorHandler`.

Spring Boot 3.x

Uses `DefaultErrorHandler`. `SeekToCurrentErrorHandler` is deprecated.

🛡️ How to Prevent This Next Time

Always configure a `DefaultErrorHandler` with a DLT recoverer to ensure poison pills don't block your consumers indefinitely.