A listener failure can requeue forever unless the container and broker have an explicit retry and dead-letter policy.
Spring AMQP poison messages: reject, requeue and dead-letter are different decisions
Classify the failure
A temporary ledger database outage may merit a bounded retry. A malformed parcel event with a missing tenant ID will not become valid after a thousand deliveries. By default, a failed listener can be requeued, so the same message may form a hot loop. AmqpRejectAndDontRequeueException marks an individual failure as non-requeueable; a broker dead-letter exchange must exist if the team wants to retain that rejected message. Kafka poison handling has a different binder and offset model.
Keep the dead-letter path observable
Record message ID, schema revision, rejection reason and tenant without dumping credentials or payload secrets. A dead-letter queue needs capacity, retention and a human replay procedure. After a parser fix, replay through the normal consumer with the business dedup key intact. Blindly returning every dead-lettered message to the main queue can recreate the original loop.
Test the broker topology
Send one invalid event and one valid event. Assert the invalid event reaches the dead-letter queue once, the valid one commits, and the listener remains available. Then test a transient failure with the configured retry limit. This code marks one rejection; without a bound queue and dead-letter exchange, the broker may discard it.
Implementation sketch
@RabbitListener(queues = "parcel-events")
public void applyParcel(ParcelEvent event) {
if (event.tenantId() == null) {
throw new AmqpRejectAndDontRequeueException("missing tenant identity");
}
parcelLedger.applyOnce(event);
}Cost and verification
Dead-letter storage and bounded retries add broker traffic. Requeue loops consume CPU and can starve valid messages, so track redelivery count and queue age.
Common Mistakes
- Do not assume rejecting without requeue automatically creates a dead-letter queue.
- Do not retry permanent validation failures indefinitely.
- Do not replay dead-letter records without a dedup and repair plan.
Read next
Spring AMQP prefetch: bound unacknowledged messages before raising concurrency, RabbitTemplate confirms and returns: broker acceptance is not consumer completion, Spring consumer deduplication: commit the event ID with the mutation, Spring outbox retry budget: park a poison event for inspection.
