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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Manual ack mode lets you distinguish permanent failures from transient ones instead of blindly requeuing everything.
- 2Rejecting with requeue=false hands poison messages to a dead-letter queue rather than looping forever.
- 3Tracking the redelivered flag prevents infinite retry loops by requeuing only on the first transient failure.
Related explainers
java
@Aspect @Component public class IdempotencyAspect {
How an idempotency aspect works in Spring
aop
idempotency
caching
Advanced
9 steps
typescript
import { useEffect, useRef, useCallback } from "react"; type DraftPayload = Record<string, unknown>;
A debounced auto-save hook in React
debouncing
request-cancellation
refs
Advanced
7 steps
java
public final class ListPartitioner { private ListPartitioner() { }
Splitting a list into fixed-size chunks in Java
generics
partitioning
streams
Intermediate
8 steps
python
import os import uuid from flask import Blueprint, current_app, jsonify, request
Handling secure file uploads in Flask
file-upload
validation
blueprints
Intermediate
8 steps
rust
use axum::{extract::{Query, State}, http::StatusCode, Json}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use serde::{Deserialize, Serialize}; use sqlx::PgPool;
Cursor pagination in an Axum handler
cursor-pagination
keyset-pagination
base64
Advanced
10 steps
java
package com.example.shop.repository; import com.example.shop.domain.Order; import com.example.shop.domain.OrderStatus;
Deriving JPA queries from method names in Spring
query derivation
repositories
pagination
Intermediate
8 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/manual-rabbitmq-acks-in-a-spring-listener-explained-java-0cc0/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.