RabbitMQ with Spring Boot

Spring Boot makes RabbitMQ integration exceptionally clean through Spring AMQP and the Spring Boot auto-configuration. With a few annotations and a starter dependency, Spring handles connection management, message conversion, retry logic, and listener containers automatically. This topic covers the Spring Boot way of working with RabbitMQ from the ground up.

Adding the Dependency

Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Gradle

implementation 'org.springframework.boot:spring-boot-starter-amqp'

Configuration in application.properties

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/

# Publisher confirms
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true

Declaring Queues, Exchanges, and Bindings

Spring AMQP uses @Bean declarations to define RabbitMQ topology. Spring creates these resources automatically when the application starts.

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    public static final String ORDER_QUEUE    = "orders";
    public static final String ORDER_EXCHANGE = "app-events";
    public static final String ROUTING_KEY    = "order.#";

    @Bean
    public Queue orderQueue() {
        return QueueBuilder.durable(ORDER_QUEUE).build();
    }

    @Bean
    public TopicExchange orderExchange() {
        return new TopicExchange(ORDER_EXCHANGE);
    }

    @Bean
    public Binding orderBinding(Queue orderQueue, TopicExchange orderExchange) {
        return BindingBuilder
            .bind(orderQueue)
            .to(orderExchange)
            .with(ROUTING_KEY);
    }
}

Publishing Messages with RabbitTemplate

RabbitTemplate is the Spring AMQP equivalent of a channel. It is auto-configured and injected wherever needed:

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final RabbitTemplate rabbitTemplate;

    public OrderService(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void placeOrder(Order order) {
        rabbitTemplate.convertAndSend(
            RabbitConfig.ORDER_EXCHANGE,
            "order.placed",
            order
        );
        System.out.println("Order event published: " + order.getOrderId());
    }
}

convertAndSend automatically serialises the Java object to JSON if Jackson is on the classpath and a Jackson2JsonMessageConverter is configured.

Configuring JSON Message Conversion

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    // ... queue/exchange/binding beans above ...

    @Bean
    public Jackson2JsonMessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(
            org.springframework.amqp.rabbit.connection.ConnectionFactory factory,
            Jackson2JsonMessageConverter converter) {
        RabbitTemplate template = new RabbitTemplate(factory);
        template.setMessageConverter(converter);
        return template;
    }
}

Consuming Messages with @RabbitListener

The @RabbitListener annotation turns any method into a message consumer. Spring handles connection, channel, prefetch, and acknowledgements automatically:

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class OrderConsumer {

    @RabbitListener(queues = RabbitConfig.ORDER_QUEUE)
    public void handleOrder(Order order) {
        System.out.println("Processing order: " + order.getOrderId());
        // Spring automatically acks on normal return
        // Throws exception → Spring nacks (requeues by default)
    }
}

Error Handling with @RabbitListener

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException;
import org.springframework.stereotype.Component;

@Component
public class OrderConsumer {

    @RabbitListener(queues = RabbitConfig.ORDER_QUEUE)
    public void handleOrder(Order order) {
        try {
            processOrder(order);
        } catch (TransientException e) {
            // Requeue for retry
            throw new org.springframework.amqp.AmqpRejectAndDontRequeueException(
                "Transient failure", e);
        } catch (PermanentException e) {
            // Dead-letter (do not requeue)
            throw new org.springframework.amqp.AmqpRejectAndDontRequeueException(
                "Permanent failure", e);
        }
    }
}

Configuring Retry with SimpleRetryPolicy

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
        org.springframework.amqp.rabbit.connection.ConnectionFactory factory) {

    SimpleRabbitListenerContainerFactory f = new SimpleRabbitListenerContainerFactory();
    f.setConnectionFactory(factory);
    f.setPrefetchCount(1);

    RetryTemplate retryTemplate = new RetryTemplate();
    retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));  // retry 3 times
    f.setRetryTemplate(retryTemplate);

    return f;
}

Dead Letter Queue Setup

@Bean
public Queue orderQueueWithDLQ() {
    return QueueBuilder.durable("orders")
        .withArgument("x-dead-letter-exchange", "dlx")
        .withArgument("x-dead-letter-routing-key", "failed-order")
        .build();
}

@Bean
public DirectExchange deadLetterExchange() {
    return new DirectExchange("dlx");
}

@Bean
public Queue deadLetterQueue() {
    return QueueBuilder.durable("failed-orders").build();
}

@Bean
public Binding deadLetterBinding() {
    return BindingBuilder
        .bind(deadLetterQueue())
        .to(deadLetterExchange())
        .with("failed-order");
}

Testing with Spring Boot Test

@SpringBootTest
@ExtendWith(SpringExtension.class)
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void testPlaceOrder() {
        Order order = new Order(9999, "TestProduct", 1);
        orderService.placeOrder(order);

        // Receive and verify the message
        Order received = (Order) rabbitTemplate.receiveAndConvert("orders", 5000);
        assertNotNull(received);
        assertEquals(9999, received.getOrderId());
    }
}

Summary

Spring Boot auto-configures RabbitMQ through spring-boot-starter-amqp. Configure connection details in application.properties. Declare topology (queues, exchanges, bindings) as @Bean methods. Inject RabbitTemplate for publishing and use @RabbitListener for consuming. Configure JSON conversion with Jackson2JsonMessageConverter for automatic Java object serialisation. Use Spring's retry and dead letter queue support for production-grade error handling without writing custom retry loops.

Leave a Comment

Your email address will not be published. Required fields are marked *