🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
org.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)
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));
}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.dtoCatch 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
}
}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);
}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
Uses `SeekToCurrentErrorHandler`.
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.