RabbitMQ with Spring Boot: Asynchronous Communication in Microservices

RabbitMQ with Spring Boot: Asynchronous Communication in Microservices
https://learn.microsoft.com/de-de/dotnet/architecture/microservices/multi-container-microservice-net-applications/rabbitmq-event-bus-development-test-environment
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

The Problem with Synchronous Communication

Concepts: Exchange, Queue, Binding

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
RabbitMQ with Spring Boot: Asynchronous Communication in Microservices