java 45 lines · 6 steps

Manual RabbitMQ acks in a Spring listener

A Spring AMQP listener that explicitly acks, rejects, or requeues order messages based on how processing fails.

Explained by highlit
1package com.example.orders.messaging;
2 
3import com.example.orders.dto.OrderEvent;
4import com.example.orders.service.OrderProcessingService;
5import com.rabbitmq.client.Channel;
6import org.slf4j.Logger;
7import org.slf4j.LoggerFactory;
8import org.springframework.amqp.rabbit.annotation.RabbitListener;
9import org.springframework.amqp.support.AmqpHeaders;
10import org.springframework.messaging.handler.annotation.Header;
11import org.springframework.messaging.handler.annotation.Payload;
12import org.springframework.stereotype.Component;
13 
14import java.io.IOException;
15 
16@Component
17public class OrderEventListener {
18 
19 private static final Logger log = LoggerFactory.getLogger(OrderEventListener.class);
20 
21 private final OrderProcessingService orderProcessingService;
22 
23 public OrderEventListener(OrderProcessingService orderProcessingService) {
24 this.orderProcessingService = orderProcessingService;
25 }
26 
27 @RabbitListener(queues = "orders.created", ackMode = "MANUAL")
28 public void onOrderCreated(
29 @Payload OrderEvent event,
30 Channel channel,
31 @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag,
32 @Header(name = AmqpHeaders.REDELIVERED, defaultValue = "false") boolean redelivered) throws IOException {
33 
34 try {
35 orderProcessingService.process(event);
36 channel.basicAck(deliveryTag, false);
37 } catch (IllegalStateException permanent) {
38 log.warn("Rejecting unprocessable order {}: {}", event.orderId(), permanent.getMessage());
39 channel.basicReject(deliveryTag, false);
40 } catch (Exception transientFailure) {
41 log.error("Failed to process order {} (redelivered={})", event.orderId(), redelivered, transientFailure);
42 channel.basicNack(deliveryTag, false, !redelivered);
43 }
44 }
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Manual ack mode lets you distinguish permanent failures from transient ones instead of blindly requeuing everything.
  2. 2Rejecting with requeue=false hands poison messages to a dead-letter queue rather than looping forever.
  3. 3Tracking the redelivered flag prevents infinite retry loops by requeuing only on the first transient failure.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Manual RabbitMQ acks in a Spring listener — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code