RabbitMQ Example Messaging with Spring Boot

In our previous article How RabbitMQ Works: Standard Message Flow and Routing Types, we explored what RabbitMQ is and how it works. Now, let’s put that knowledge into practice by implementing messaging in Spring Boot. We will create two projects—one for sending messages (the producer) and one for receiving them (the consumer).

1. What We Are Doing

In this example, we implement a monitoring solution for devices. The producer sends messages containing device status information using dynamic routing keys (such as monitoring.laptop, monitoring.phone, and monitoring.tablet). The consumer subscribes to corresponding queues and receives these messages.

2. Technologies Used

Our projects leverage the following technologies and tools:

  • Spring AMQP – Provides integration with RabbitMQ for messaging.
  • RabbitMQ – Acts as the message broker to route and transmit messages.

Both projects share common dependencies as defined in the pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3. Project Setup

Both the producer and consumer projects are created using the Spring Initializr. When setting up your projects, be sure to add the Spring AMQP dependency to enable RabbitMQ support.

Additionally, to run RabbitMQ, you can quickly set up a broker using Docker. Use the following command to start RabbitMQ with the management plugin:

docker run -d --hostname devblueprint-rabbit --name devblueprint-rabbit-mq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

This command does the following:

  • Runs RabbitMQ in a detached mode.
  • Sets a custom hostname (devblueprint-rabbit) and container name (devblueprint-rabbit-mq).
  • Maps port 5672 (for AMQP protocol) and 15672 (for the management interface) from the container to your local machine.
  • Uses the rabbitmq:3-management image, which includes the RabbitMQ management plugin for easier monitoring and management.

4. Project Implementation

Producer Project

Key Components:

RabbitMQConfig.class (Producer Configuration):

package tech.devblueprint.rabbitmq_producer_spring_boot_example.configuration;

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

@Configuration
public class RabbitMQConfig {

    public static final String EXCHANGE_NAME = "monitoring.exchange";

    // Create a topic exchange
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(EXCHANGE_NAME);
    }
}


This configuration class defines a Topic Exchange with the name monitoring.exchange. The exchange is responsible for routing messages based on specific routing keys, which allows the producer to target messages to different consumer queues.

MessageProducer.class (Message Sending Service):

package tech.devblueprint.rabbitmq_producer_spring_boot_example.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import tech.devblueprint.rabbitmq_producer_spring_boot_example.configuration.RabbitMQConfig;

@Service
public class MessageProducer {

    private final RabbitTemplate rabbitTemplate;

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

    // Sends a monitoring message using a specific routing key
    public void sendMessage(String routingKey, String message) {
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, routingKey, message);
    }
}

This service encapsulates the logic for sending messages. It uses Spring’s RabbitTemplate to send a message by taking a routing key and a message string as input and then publishing the message to the defined exchange.

ScheduledMessageSender.class (Scheduled Message Sender):

package tech.devblueprint.rabbitmq_producer_spring_boot_example.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledMessageSender {

    private static final Logger logger = LoggerFactory.getLogger(ScheduledMessageSender.class);

    private final MessageProducer messageProducer;
    private int counter = 0;

    public ScheduledMessageSender(MessageProducer messageProducer) {
        this.messageProducer = messageProducer;
    }

    // Sends a monitoring message every 30 seconds with cycling routing keys
    @Scheduled(fixedDelay = 30000)
    public void sendMonitoringMessage() {
        // Routing keys representing different device types
        String[] routingKeys = {"monitoring.laptop", "monitoring.phone", "monitoring.tablet"};
        String routingKey = routingKeys[counter % routingKeys.length];
        counter++;

        // Example monitoring message with simulated data
        String message = String.format("Device monitoring data at %d: status OK for %s", 
                System.currentTimeMillis(), routingKey);
        logger.info(message);
        messageProducer.sendMessage(routingKey, message);
    }
}

Leveraging the @Scheduled annotation, this component sends monitoring messages at fixed intervals. It cycles through an array of routing keys (such as monitoring.laptop, monitoring.phone, and monitoring.tablet) and sends a message containing dynamic content (like current timestamp and device status) each time it runs.

Consumer Project

Key Components:

RabbitMQConfig.class (Consumer Configuration):

package tech.devblueprint.rabbitmq_consumer_spring_boot_example.configuration;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    public static final String EXCHANGE_NAME = "monitoring.exchange";
    public static final String QUEUE_LAPTOP = "queue.monitoring.laptop";
    public static final String QUEUE_PHONE = "queue.monitoring.phone";
    public static final String QUEUE_TABLET = "queue.monitoring.tablet";

    // Create a topic exchange
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(EXCHANGE_NAME);
    }

    // Create queues for different device types
    @Bean
    public Queue laptopQueue() {
        return new Queue(QUEUE_LAPTOP);
    }

    @Bean
    public Queue phoneQueue() {
        return new Queue(QUEUE_PHONE);
    }

    @Bean
    public Queue tabletQueue() {
        return new Queue(QUEUE_TABLET);
    }

    // Bind queue for laptop monitoring messages
    @Bean
    public Binding bindingLaptop(Queue laptopQueue, TopicExchange topicExchange) {
        return BindingBuilder.bind(laptopQueue).to(topicExchange).with("monitoring.laptop");
    }

    // Bind queue for phone monitoring messages
    @Bean
    public Binding bindingPhone(Queue phoneQueue, TopicExchange topicExchange) {
        return BindingBuilder.bind(phoneQueue).to(topicExchange).with("monitoring.phone");
    }

    // Bind queue for tablet monitoring messages
    @Bean
    public Binding bindingTablet(Queue tabletQueue, TopicExchange topicExchange) {
        return BindingBuilder.bind(tabletQueue).to(topicExchange).with("monitoring.tablet");
    }
}


In the consumer project, this configuration class creates queues for different device types: laptop, phone, and tablet. It also sets up bindings between these queues and the topic exchange using their respective routing keys. This ensures that messages sent with a particular routing key will be delivered to the corresponding queue.

MessageConsumer.class (Message Consumption Service):

package tech.devblueprint.rabbitmq_consumer_spring_boot_example.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import tech.devblueprint.rabbitmq_consumer_spring_boot_example.configuration.RabbitMQConfig;

@Service
public class MessageConsumer {

    private static final Logger logger = LoggerFactory.getLogger(MessageConsumer.class);

    // Listener for laptop monitoring messages
    @RabbitListener(queues = RabbitMQConfig.QUEUE_LAPTOP)
    public void receiveLaptopMessage(String message) {
        logger.info("Received on {}: {}", RabbitMQConfig.QUEUE_LAPTOP, message);
    }

    // Listener for phone monitoring messages
    @RabbitListener(queues = RabbitMQConfig.QUEUE_PHONE)
    public void receivePhoneMessage(String message) {
        logger.info("Received on {}: {}", RabbitMQConfig.QUEUE_PHONE, message);
    }

    // Listener for tablet monitoring messages
    @RabbitListener(queues = RabbitMQConfig.QUEUE_TABLET)
    public void receiveTabletMessage(String message) {
        logger.info("Received on {}: {}", RabbitMQConfig.QUEUE_TABLET, message);
    }
}

 


This service listens for messages using the @RabbitListener annotation on each queue. When a message arrives in any of the queues, the corresponding method is triggered, and the received message is logged. This helps in monitoring and verifying that messages are being correctly routed and processed.

5. Tests

Run both applications and you will see:

Conclusion

This article demonstrates how to implement messaging between two Spring Boot applications using RabbitMQ.