1. Introduction
RabbitMQ is an open-source messaging broker that supports different messaging protocols. When used with Spring boot, it can be configured in Java with the support of Spring annotations, and such configuration is straightforward to understand, grow and expand. This is the Spring AMQP Java Configuration guide.
2. Prepare the Environment
This article requires a RabbitMQ server running locally. This can be accomplished quickly using Docker. If you are unfamiliar with Docker containers, the initial setup is available in this article on how to run RabbitMQ in a Docker container.
In case you are feeling baffled by RabbitMQ? I understand–it’s easy to get tangled up in tech talk. Don’t stress, though: a quick read about RabbitMQ architecture will have you hop ahead confidently!
Next, we generate a blank Spring boot project template using Spring Initializr with all the required dependencies. Click generate and import into your IDE.
2.1. The RabbitMQ Diagram
So we are going to create a fanout exchange that routes messages to the queue, which will then be consumed by our consumer application that we are going to create. We will simulate an exception that will route the message to the DLQ (Dead Letter Queue), which can be used for reprocessing and analysis.
3. Define Spring AMQP Exchange
RabbitMQ Exchange is an orchestration component where messages are sent to different queues using binding and routing keys. Let’s start by defining one in our application. In the code below, we have created a property file that will hold the names of all RabbitMQ resources in one place.
public class RabbitProperties { public static final String INCOMING_EXCHANGE_NAME = "incoming.msg"; }
We can pass those names to define our RabbitMQ infrastructure inside a Spring boot @Configuration file. The incoming exchange will be injected into the application startup since we have defined it as a Spring bean.
@Data @Configuration public class RabbitConfiguration { @Bean public FanoutExchange incomingExchange() { return new FanoutExchange(INCOMING_EXCHANGE_NAME); } }
Similarly, we have created another exchange responsible for holding all the error messages during the processing. Depending on the exchange type, we could use routing keys to determine routing for messages if multiple consumers are listening. However, fanout exchange doesn’t require a routing key and will pass all the messages to all the bound queues.
public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
@Bean public FanoutExchange deadLetterExchange() { return new FanoutExchange(DEAD_LETTER_EXCHANGE); }
4. Queues With Exchange Bindings
While the exchanges can accept messages, without queues, we won’t be able to route those messages for processing. So we have created two additional Spring beans in our configuration class to represent two queues, one will handle incoming messages for processing, and the other will contain errors.
The main processing queue contains an argument that tells RabbitMQ where to route the message when rejected due to a processing error or a failed delivery.
public static final String INCOMING_QUEUE_NAME = "inc.msg.tech4gods"; public static final String ERROR_QUEUE = "dlq.inc.msg.tech4gods";
@Bean public Queue incomingQueue() { return QueueBuilder.durable(INCOMING_QUEUE_NAME) .withArgument("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE) .build(); } @Bean public Queue deadLetterQueue() { return new Queue(ERROR_QUEUE); }
Binding in RabbitMQ is the connection between an exchange and a queue. It can be achieved using a routing key that matches messages published to exchange with the queues bound to it. The binding also allows dead letter exchanges (DLX) and dead letter routing keys (DLK) to be configured, which specify where send messages should go if they are rejected or failed delivery attempts.
With Spring Boot, message binding can be easily automated when configuring RabbitMQ message broker components.
@Bean public Binding incomingBinding() { return BindingBuilder .bind(incomingQueue()) .to(incomingExchange()); } @Bean public Binding deadLetterBinding() { return BindingBuilder .bind(deadLetterQueue()) .to(deadLetterExchange()); }
The following binding binds both exchanges and queues together. When an exchange receives a message, it will be sent to a queue.
We added the listener and component annotation to register the broker connection on startup and enabled the consumer to listen for messages on the defined queue. Also, we don’t need to worry about the configuration of connection management with RabbitMQ because Spring boot AMQP will configure that automatically.
@Slf4j @Component public class RabbitConsumer { @RabbitListener(queues = {RabbitProperties.INCOMING_QUEUE_NAME}) public void consume(String message) { log.info(String.format("Received message: %s", message)); } }
Since we have a RabbitMQ server running locally via Docker, we can start the project with the above configuration, and all RabbitMQ resources should be created on startup automatically. This includes all the bean definitions we have configured up to this point.
Let’s verify that on the management page at http://localhost:15672/. Login using default credentials (guest/guest), and there we have exchanges and queues tabs showing the resources we have created.
To verify that everything works as expected let’s publish a message on the exchange.
The log message in the spring boot application demonstrates that everything works as expected.
4.1. DLQ (Dead Letter Queue) Routing
Although this tutorial won’t delve into error handling in-depth, we’ll take a moment to verify that the DLQ was created without issue.
Let’s amend the consumer class to throw an exception when receiving the message.
@Slf4j @Component public class RabbitConsumer { @RabbitListener(queues = {RabbitProperties.INCOMING_QUEUE_NAME}) public void consume(String message) { log.info(String.format("Received message: %s", message)); throw new IllegalArgumentException("Simulated exception"); } }
Add the following bean to the RabbitConfiguration class to disable the default queue logic.
@Bean("rabbitListenerContainerFactory") public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory (ConnectionFactory connectionFactory) { var factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setDefaultRequeueRejected(false); return factory; }
Start the spring boot application, post another message via the management console and check that the message has been routed to the DLQ.
5. RabbitMQ Message properties
We can also define global message properties for each RabbitMQ message, and different properties can be set using the Spring MessageProperties bean. Several properties can be configured to project needs, but I will leave those for your investigation.
In this example, we have set the message’s content type and how it is encoded.
@Bean public MessageProperties rabbitMessageProperties() { MessageProperties msgProperties = new MessageProperties(); msgProperties.setContentEncoding("UTF-8"); msgProperties.setContentType("application/json"); return msgProperties; }
6. Summary
In this article, w have looked at Spring AMQP Java Configuration example. With Spring Java, configuring RabbitMQ for your application is a breeze! It’s as easy as defining the resources you need on startup — just like that, and voila: all set to move forward with the project.
Daniel Barczak
Daniel Barczak is a software developer with a solid 9-year track record in the industry. Outside the office, Daniel is passionate about home automation. He dedicates his free time to tinkering with the latest smart home technologies and engaging in DIY projects that enhance and automate the functionality of living spaces, reflecting his enthusiasm and passion for smart home solutions.
Leave a Reply