Home / Spring Boot / Messaging and Microservices / RabbitMQ with Spring Boot (AMQP)

RabbitMQ with Spring Boot (AMQP)

RabbitMQ is a message broker that routes messages to queues using exchanges. It is a good fit for task queues, work distribution and flexible routing, where each message is handled by one consumer and then removed.

AMQP model

Producer ─► Exchange ── binding (routing key rule) ──► Queue ──► Consumer
                 │                                            └─────► Consumer  (competing consumers share the queue)
                 ├── binding ──► Queue B ──► Consumer

The producer never sends to a queue directly. It sends to an EXCHANGE with a ROUTING KEY.
The exchange type decides which queues get the message.
Exchange typeRoutingExample
directExact routing key matchorder.created goes to the order queue
topicPattern match (* one word, # many words)order.* or payment.#
fanoutAll bound queues, key ignoredBroadcast to every service
headersMatch on message headersRare

Dependency and configuration

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        acknowledge-mode: auto
        prefetch: 10
        concurrency: 2
        max-concurrency: 8
        retry:
          enabled: true
          initial-interval: 1s
          max-attempts: 4
          multiplier: 2.0

Declare exchange, queues, dead letter queue

@Configuration
public class RabbitConfig {

    public static final String EXCHANGE = "shop.exchange";
    public static final String QUEUE = "order.created.queue";
    public static final String DLX = "shop.dlx";
    public static final String DLQ = "order.created.dlq";

    @Bean TopicExchange shopExchange() { return new TopicExchange(EXCHANGE); }
    @Bean DirectExchange deadLetterExchange() { return new DirectExchange(DLX); }

    @Bean
    Queue orderQueue() {
        return QueueBuilder.durable(QUEUE)
                .withArgument("x-dead-letter-exchange", DLX)             // rejected messages go here
                .withArgument("x-dead-letter-routing-key", DLQ)
                .build();
    }
    @Bean Queue deadLetterQueue() { return QueueBuilder.durable(DLQ).build(); }

    @Bean Binding orderBinding() { return BindingBuilder.bind(orderQueue()).to(shopExchange()).with("order.created"); }
    @Bean Binding dlqBinding() { return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(DLQ); }

    @Bean
    MessageConverter jsonConverter() { return new Jackson2JsonMessageConverter(); }     // JSON instead of Java serialization
}

Producer and consumer

public record OrderCreated(String orderId, BigDecimal total) { }

@Service
public class OrderPublisher {
    private final RabbitTemplate rabbit;
    public OrderPublisher(RabbitTemplate rabbit) { this.rabbit = rabbit; }

    public void publish(OrderCreated event) {
        rabbit.convertAndSend(RabbitConfig.EXCHANGE, "order.created", event);
    }
}

@Component
public class OrderConsumer {

    @RabbitListener(queues = RabbitConfig.QUEUE)
    public void handle(OrderCreated event) {
        if (event.total().signum() < 0) {
            throw new AmqpRejectAndDontRequeueException("invalid total");     // straight to the dead letter queue
        }
        System.out.println("Processing " + event.orderId());                  // exception = retry, then DLQ
    }
}

Message life cycle and acknowledgements

1. Producer publishes (optionally with publisher confirms: broker confirms it stored the message)
2. Exchange routes to queue(s)   (if no queue matches the message is dropped, unless mandatory/returns are set)
3. Broker delivers up to `prefetch` unacknowledged messages to a consumer
4. Listener succeeds ─► ack ─► message removed from the queue
   Listener throws   ─► retry (spring retry) ─► still failing ─► reject without requeue ─► dead letter exchange ─► DLQ
5. Consumer dies before ack ─► broker requeues the message to another consumer

RabbitMQ or Kafka?

PointRabbitMQKafka
ModelSmart broker, message removed after ackLog, consumers keep offsets, data kept by retention
RoutingVery flexible (exchanges)Topic and partition only
ReplayNo (unless streams plugin)Yes
OrderingPer queue with one consumerPer partition
Best forTask queues, RPC style, complex routingEvent streams, analytics, event sourcing, very high volume

Common mistake: Using the default guest user and no dead letter queue in production. Create real users, and always define a dead letter queue so failing messages are stored for inspection and not lost or endlessly retried.

Interview questions

What is the difference between a queue and an exchange?

A queue stores messages. An exchange receives messages from producers and routes them to queues using bindings and routing keys.

How do I process messages in parallel?

Use several consumers on the same queue (competing consumers) with concurrency and max-concurrency. Ordering between them is not guaranteed.

What happens if the broker restarts?

Durable queues and persistent messages survive. Non-durable queues and transient messages are lost.