A Kafka binder dead-letter topic is an explicit failure route after configured attempts; it is not a fix for the failed record.
Spring Cloud Stream Kafka DLQ: stop poison messages without losing the trail
Bind failures to a named group
The Kafka binder's DLQ path needs a consumer group and explicit enablement. After the configured attempts are exhausted, a failing parcel message can be published to a dead-letter topic. The application must decide who inspects, repairs and replays that record. The listener failure lesson distinguishes transient errors from bad data.
Keep replay bounded
Attach the original event ID, failure class and attempt count to an operational record. A replay worker must not blindly put a permanently invalid message back on the main topic: that creates a loop. Send repaired records under a new correction ID or preserve the original ID with a documented dedup policy. Idempotency state and business uniqueness need to agree on that choice.
Verify the binder's actual behavior
Force one valid record to fail twice and then succeed; force another to fail beyond the limit. Assert the first reaches business state once and the second appears in the configured DLQ with enough context to repair it. This cannot be proven by a unit test of the Consumer function alone.
Implementation sketch
spring:
cloud:
stream:
bindings:
settleParcel-in-0:
destination: parcel-events
group: settlement-workers
consumer:
max-attempts: 3
kafka:
bindings:
settleParcel-in-0:
consumer:
enable-dlq: trueCost and verification
Each retry consumes processing capacity and can block later records on the same partition. DLQ storage grows until records are triaged and retained or removed under policy.
Common Mistakes
- Do not expect DLQ routing without the binder-specific setting and named group.
- Do not replay every DLQ record automatically.
- Do not discard the original event ID or error reason during repair.
Read next
Spring Cloud Stream consumer group: scale one logical reader without copying every event, Spring Kafka listener failures: separate transient retries from poison records, Spring Integration idempotent receiver: the metadata key is not the business commit, Spring outbox retry budget: park a poison event for inspection.
