
by Zelkulon07 March 20251 min read
How RabbitMQ decouples microservices, delivers messages reliably and Spring AMQP makes integration a breeze.
The Problem with Synchronous Communication

Concepts: Exchange, Queue, Binding

- Exchange: Receives messages and decides which queue they go to
- Queue: Stores messages until a consumer picks them up
- Binding: Connects exchange with queue (via routing key)
- Routing Key: String key for routing (e.g. order.created)
Configuration: Exchanges & Queues
@Configuration
public class RabbitMQConfig {
public static final String ORDER_EXCHANGE = "order.exchange";
public static final String ORDER_CREATED_QUEUE = "order.created.queue";
public static final String ORDER_CREATED_KEY = "order.created";
@Bean
public TopicExchange orderExchange() {
return new TopicExchange(ORDER_EXCHANGE, true, false);
}
@Bean
public Queue orderCreatedQueue() {
return QueueBuilder.durable(ORDER_CREATED_QUEUE)
.withArgument("x-dead-letter-exchange", "")
.withArgument("x-dead-letter-routing-key", "dead.letter.queue")
.build();
}
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}Producer: Sending Messages
@Service
@RequiredArgsConstructor
public class OrderEventPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishOrderCreated(Order order) {
var event = new OrderCreatedEvent(
order.getId(), order.getCustomerId(),
order.getTotalAmount(), Instant.now()
);
rabbitTemplate.convertAndSend(
RabbitMQConfig.ORDER_EXCHANGE,
RabbitMQConfig.ORDER_CREATED_KEY,
event
);
}
}
// Important: send event only AFTER the DB commit!
@Transactional
public OrderResponse placeOrder(OrderRequest request) {
Order saved = orderRepository.save(buildOrder(request));
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override public void afterCommit() {
eventPublisher.publishOrderCreated(saved);
}
}
);
return toResponse(saved);
}Consumer: Receiving Messages
@Component
@RequiredArgsConstructor
public class OrderCreatedListener {
@RabbitListener(queues = RabbitMQConfig.ORDER_CREATED_QUEUE)
public void handleOrderCreated(
OrderCreatedEvent event,
Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag
) {
try {
paymentService.processPayment(event);
channel.basicAck(deliveryTag, false); // Success
} catch (PaymentException e) {
// Not retryable β Dead Letter Queue
channel.basicNack(deliveryTag, false, false);
} catch (Exception e) {
// Retry β back to queue
channel.basicNack(deliveryTag, false, true);
}
}
}Summary
- Decoupling: services don't know each other
- Fault tolerance: messages survive service outages
- Dead Letter Queue: no data loss on processing errors
- Scalability: multiple consumer instances process in parallel
- Transactional safety: send event only after DB commit