01Rate limiting02Databases03Caching04Flash sale05Idempotency06Circuit breaker07Queues & DLQ

Lab / 07 · Queues

Messages fail, back off, retry, and land safely in a DLQ.

When asynchronous background tasks fail, a naive consumer crashes in an infinite loop, blocking all downstream traffic. An event-driven DLQ architecture retries with exponential backoff and isolates poison pills without message loss.

LIVE · Max 3 Retries · Exponential Backoff & DLQ
Processed: 0In DLQ: 0
Main Queue (3)tasks.inbox
MSG-001
Generate monthly invoice PDF
MSG-002
[POISON_PILL] Malformed JSON metadata
MSG-003
Send welcome onboarding email
Retry Queue (0)tasks.retry
No active retries
Dead Letter Queue (0)tasks.dlq
Zero dead letters (Healthy)
Completed (0)ACK / 200
None processed yet

On the live site, messages are routed through real Spring Boot event channels with exponential backoff and replay capabilities.

Dead Letter Queue Lifecycle

Main Queue → Consumer attempts processing → on failure, moves to Retry Queue with backoff (2s, 4s, 8s) → after 3 attempts, moves to Dead Letter Queue (DLQ).

Producer
API / Webhook
Main Queue
tasks.inbox
Retry Queue
Exponential backoff
Worker Consumer
Async processor
Dead Letter Queue
tasks.dlq (Safe park)
Poison Pill: 3 retries with backoff → safely parked in DLQ | Zero head-of-line blocking for healthy messages

Spring Kafka / Consumer DLQ Handler

@Bean public DefaultErrorHandler errorHandler(KafkaTemplate<String, Object> template) { // Exponential backoff: 1s initial, 2.0x multiplier, max 3 attempts ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0); backOff.setMaxAttempts(3); DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(template, (record, ex) -> new TopicPartition("tasks.dlq", record.partition()) ); return new DefaultErrorHandler(recoverer, backOff); }
Full source on GitHub →

Trade-offs I weighed

In-Memory Retries vs Dedicated Retry Topics

Retrying in memory with Thread.sleep blocks the Kafka consumer partition, preventing other messages from advancing. Publishing failed records to dedicated retry topics maintains pipeline throughput.

At-Least-Once Delivery vs At-Most-Once

At-least-once with idempotent consumer processing ensures zero data loss during broker rebalances or node failures, whereas at-most-once drops messages whenever consumers restart.

Manual DLQ Redrive vs Automatic Reprocessing

Poison pills caused by schema drift or bugs require code fixes before redriving. Manual, audited replay tools ensure fixed messages do not fail a second time.

← ← 06 Circuit breakerNext: why backend is the hero of AI → →