Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 77

RabbitMQ Messaging with Spring Boot

Recommended resources

Hello! Welcome to the final lesson in our module on Asynchronous and Event-Driven Patterns.

In our last session, you successfully implemented a message producer. Your Spring Boot application can now send a message to a RabbitMQ topic exchange, which then routes it to a queue named my-queue. That message is currently sitting in the queue, waiting to be processed.

Today, we will complete the circuit. Our goal is to implement a message consumer in Spring Boot to receive and process messages from that RabbitMQ queue. By the end of this lesson, you will have a complete, self-contained messaging application where one part produces a message and another part consumes it automatically.

The Consumer's Role in Messaging

Before we write the code, let's revisit the core architecture of RabbitMQ to understand the consumer's place in it.

RabbitMQ Message Flow Architecture
This diagram illustrates the flow of messages in RabbitMQ. Producers (Publishers) send messages to an Exchange, which routes them to Queues. Consumers then connect to these queues to receive and process the messages.

As you can see, the Consumer is the final destination for a message. It's a program that connects to a queue, retrieves messages, and performs some work based on their content. This decouples the work from the initial request, allowing for robust, scalable, and resilient systems. In our case, the "work" will simply be printing the message to the console.


Implementing a Message Listener in Spring AMQP

In Spring's AMQP (Advanced Message Queuing Protocol) support, the easiest way to create a consumer is by creating a message listener. This is a component that Spring automatically connects to a queue, invoking a method in your code whenever a message arrives.

The framework provides a simple and powerful annotation for this: @RabbitListener. Let's build our consumer.

We will use the same project from the previous lesson. The official RabbitMQ "Hello World" tutorial provides a very clean and simple example of a receiver, which we will adapt.

RabbitMQ tutorial - "Hello World!"

The official RabbitMQ tutorial demonstrates a straightforward receiver implementation. We'll use this as a model for our own consumer.

Please review the section titled 'Receiving'. Focus on the Tut1Receiver.java class. Pay close attention to the @RabbitListener and @RabbitHandler annotations, as these are the key components we will be using.

Now, let's apply this to our project.

  1. Create a new Java class named Receiver in the same package as your MessagingRabbitmqApplication.java and Runner.java files.

  2. Add the following code to your new Receiver.java file.

package com.example.messagingrabbitmq; // Use your own package name

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

@Component
public class Receiver {

    @RabbitListener(queues = "my-queue")
    public void receiveMessage(String message) {
        System.out.println("Received <" + message + ">");
    }
}

Let's break down this simple yet powerful class:

  • @Component: This annotation tells Spring to create a bean of this class, making it a managed component in the application context. This is crucial for Spring to detect the listener.
  • @RabbitListener(queues = "my-queue"): This is the core of our consumer. It instructs Spring to:
    • Create a message listener container under the hood.
    • Have this container listen to the queue named my-queue, which is the same queue our producer is sending messages to.
    • When a message arrives, invoke the annotated method (receiveMessage).
  • public void receiveMessage(String message): This is our handler method. Spring AMQP intelligently converts the body of the RabbitMQ message into the type of the method parameter. Since our producer sent a String, we can declare a String parameter here to receive the payload directly.

That's it! With just these few lines of code, you have a fully functional message consumer.

Test your understanding!

Imagine you are building a notification service. You need to create a consumer that processes email notifications from a queue named email-notifications. What is the single most important change you would make to the Receiver class above to make it listen to that queue instead?

Show answer

You would change the @RabbitListener annotation to target the new queue name:
@RabbitListener(queues = "email-notifications")


Running the Full Producer-Consumer Cycle

Your project now contains both the Runner.java class (the producer) from the last lesson and the new Receiver.java class (the consumer). When you run the application, the following will happen in sequence:

  1. The Spring application starts.
  2. Spring detects the @RabbitListener on your Receiver bean and starts a listener container in the background, which connects to RabbitMQ and waits for messages on my-queue.
  3. Spring executes the run method of your CommandLineRunner (Runner.java).
  4. The Runner sends the message "Hello from our Spring Boot producer!" to the my-topic-exchange.
  5. The exchange routes the message to my-queue.
  6. The listener, which has been waiting patiently, immediately picks up the message and invokes your receiveMessage method.
  7. The "Received <...>" message is printed to the console.

Let's see it in action:

  1. Make sure your RabbitMQ Docker container is running (docker-compose up -d).
  2. Run your Spring Boot application from your IDE.
  3. Observe the console output. You should see something like this:
Sending message...
Message sent!
Received <Hello from our Spring Boot producer!>

You might notice the "Received" message appears almost instantaneously after the "Sent" message. This demonstrates the power of asynchronous, event-driven communication.

Now, check the RabbitMQ Management UI:

  1. Open http://localhost:15672 in your browser.
  2. Go to the Queues tab.
  3. Find my-queue in the list.

Unlike last time, you will likely see 0 in the "Messages" column. Why? Because the Receiver consumed the message as soon as it arrived. You might see the message rate graphs spike for a second, indicating that a message was enqueued and then immediately dequeued.

This is what a healthy consumer looks like in action—it processes messages as they come in, keeping the queue empty. For comparison, the image below shows what a message looks like when it's waiting in the queue before being consumed.

RabbitMQ Management UI: Retrieving a Message from a Queue
This image from the RabbitMQ management UI shows the details of a message waiting in a queue. With our running listener, messages are consumed so quickly they often don't remain in this state for long.

Conclusion

Congratulations! You have successfully built a complete messaging loop. You now have a Spring Boot application that can both produce and consume messages via RabbitMQ. This forms the foundation for building complex, event-driven architectures and microservices.

Key Takeaways:

  • A message consumer (or receiver/listener) is a component that subscribes to a queue and processes incoming messages.
  • The @RabbitListener(queues = "...") annotation is the primary way to create a message consumer in Spring Boot.
  • Spring Boot's auto-configuration handles the complex work of setting up listener containers, allowing you to focus on the business logic of your handler method.
  • The producer and consumer are decoupled; the producer sends a message without knowing who, if anyone, is listening. The consumer processes messages without knowing who sent them.

What's Next?

You have now mastered the fundamental patterns of asynchronous communication within a single application. This is a critical skill, but in modern systems, this pattern is most powerful when used to enable communication between different applications or services.

This brings us to the end of the "Asynchronous & Event-Driven Patterns" module and sets the stage perfectly for our next major topic: Microservices with Spring Cloud. In the first lesson of the next module, we will explore the core principles of microservices and discuss strategies for breaking down a large, monolithic application into smaller, independent services that can communicate using the very patterns you've just learned.