java
60 lines · 9 steps
Dead-letter recovery with Spring AMQP
A Spring component consumes dead-lettered orders, tracks retry attempts, and either re-queues with backoff or escalates.
Explained by
highlit
1@Component
2public class OrderRecoveryHandler {
3
4 private final RabbitTemplate rabbitTemplate;
5 private final RecoveryAttemptRepository attempts;
6 private final AlertService alertService;
7
8 public OrderRecoveryHandler(RabbitTemplate rabbitTemplate,
9 RecoveryAttemptRepository attempts,
10 AlertService alertService) {
11 this.rabbitTemplate = rabbitTemplate;
12 this.attempts = attempts;
13 this.alertService = alertService;
14 }
15
16 @RabbitListener(queues = "orders.payment.dlq")
17 public void onPaymentFailure(FailedOrderMessage message,
18 @Header("x-death") List<Map<String, ?>> death) {
19 recover(message, RecoverySource.PAYMENT, deathCount(death));
20 }
21
22 @RabbitListener(queues = "orders.fulfillment.dlq")
23 public void onFulfillmentFailure(FailedOrderMessage message,
24 @Header("x-death") List<Map<String, ?>> death) {
25 recover(message, RecoverySource.FULFILLMENT, deathCount(death));
26 }
27
28 private void recover(FailedOrderMessage message, RecoverySource source, long deaths) {
29 RecoveryAttempt attempt = attempts.findByOrderId(message.orderId())
30 .orElseGet(() -> new RecoveryAttempt(message.orderId()));
31 attempt.record(source, message.reason());
32
33 if (deaths >= 3 || attempt.hasFailedIn(RecoverySource.PAYMENT, RecoverySource.FULFILLMENT)) {
34 attempt.escalate();
35 attempts.save(attempt);
36 alertService.pageOnCall(message.orderId(), attempt.summary());
37 return;
38 }
39
40 attempts.save(attempt);
41 rabbitTemplate.convertAndSend(
42 "orders.retry.exchange",
43 source.routingKey(),
44 message,
45 m -> {
46 m.getMessageProperties().setDelayLong(backoffMillis(deaths));
47 return m;
48 });
49 }
50
51 private long deathCount(List<Map<String, ?>> death) {
52 return death == null || death.isEmpty()
53 ? 1L
54 : ((Number) death.get(0).getOrDefault("count", 1L)).longValue();
55 }
56
57 private long backoffMillis(long deaths) {
58 return Math.min(Duration.ofMinutes(5).toMillis(), 2000L * (1L << deaths));
59 }
60}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Dead-letter queues plus a persisted attempt record let you retry failures safely without losing track of history.
- 2Capping exponential backoff prevents retry delays from growing unbounded while still spacing out attempts.
- 3Escalation rules based on death count and cross-source failure stop poison messages from retrying forever.
Related explainers
java
@Component public class CorrelationIdWebFilter implements WebFilter, Ordered { public static final String CORRELATION_ID_HEADER = "X-Correlation-Id";
Correlation IDs in a Spring WebFlux filter
reactive
web filter
correlation id
Advanced
7 steps
java
public final class DeepCopier { private static final ObjectMapper MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule())
Deep-copying objects via Jackson serialization
serialization
deep-copy
generics
Intermediate
7 steps
java
package com.example.time; import java.time.Duration; import java.time.Instant;
Adding ISO-8601 durations across time zones in Java
date-time
iso-8601
parsing
Intermediate
7 steps
java
@Component public class InventoryReconciliationJob { private static final Logger log = LoggerFactory.getLogger(InventoryReconciliationJob.class);
Distributed scheduled jobs with Spring locks
distributed-locking
scheduling
concurrency
Advanced
8 steps
java
import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Pattern;
Ranking the most frequent words in Java
streams
regex
grouping
Intermediate
7 steps
java
public final class HttpHeaders { private final NavigableMap<String, List<String>> values = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
A case-insensitive HTTP headers map in Java
case-insensitivity
multimap
immutability
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/dead-letter-recovery-with-spring-amqp-explained-java-5f8b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.