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

Walkthrough

Space play step click any line
Three takeaways
  1. 1Dead-letter queues plus a persisted attempt record let you retry failures safely without losing track of history.
  2. 2Capping exponential backoff prevents retry delays from growing unbounded while still spacing out attempts.
  3. 3Escalation rules based on death count and cross-source failure stop poison messages from retrying forever.

Related explainers

java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
java
public static Map<String, String> parseCookieHeader(String header) {
    Map<String, String> cookies = new LinkedHashMap<>();
    if (header == null || header.isBlank()) {
        return cookies;

Parsing an HTTP Cookie header in Java

string-parsing http url-decoding
Intermediate 6 steps
java
public class TimedSocketReader {
 
    private static final int READ_TIMEOUT_MS = 5_000;
    private static final int CONNECT_TIMEOUT_MS = 3_000;

Reading a socket with connect and read timeouts

sockets timeouts io
Intermediate 8 steps
java
public final class EncodingDetector {
 
    public enum Encoding {
        UTF_8, UTF_16LE, UTF_16BE, UTF_32LE, UTF_32BE, ASCII, UNKNOWN

Detecting text encoding from raw bytes in Java

byte-manipulation encoding-detection bitwise-operations
Intermediate 8 steps
java
public final class ImportOrganizer {
 
    private static final Pattern IMPORT_LINE =
            Pattern.compile("^import\\s+(static\\s+)?([\\w.]+(?:\\.\\*)?)\\s*;\\s*$");

Sorting Java imports with a regex pass

regex sorting text-processing
Intermediate 8 steps

Share this explainer

Here's the card — post it anywhere.

Dead-letter recovery with Spring AMQP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code