The Price of all “Learn Spring Security” course packages will permanently increase by $50 on the 8th of December:

>>> GET ACCESS NOW

1. Introduction

In this article, we’ll explore the concept of fanout and topic exchanges with Spring AMQP and RabbitMQ.

At a high level, fanout exchanges will broadcast the same message to all bound queues, while topic exchanges use a routing key for passing messages to a particular bound queue or queues.

Prior reading of Messaging With Spring AMQP is recommended for this article.

2. Setting Up a Fanout Exchange

Let’s set up one fanout exchange with two bound queues.

Spring AMQP allows us to aggregate all the declarations of queues, exchanges, and bindings from Java configurations in a collection and returned as a List<Declarable>:

@Bean
public List<Declarable> fanoutBindings() {
    Queue fanoutQueue1 = new Queue("fanout.queue1", false);
    Queue fanoutQueue2 = new Queue("fanout.queue2", false);
    FanoutExchange fanoutExchange = new FanoutExchange("fanout.exchange");

    return Arrays.asList(
      fanoutQueue1,
      fanoutQueue2,
      fanoutExchange,
      bind(fanoutQueue1).to(fanoutExchange),
      BindingBuilder.bind(fanoutQueue2).to(fanoutExchange));
}

With this configuration, we expect that both queues will get all the messages sent to this exchange.

3. Setting Up a Topic Exchange

We’ll also set up a topic exchange with two bound queues each with specific routing keys:

@Bean
public List<Declarable> topicBindings() {
    Queue topicQueue1 = new Queue(topicQueue1Name, false);
    Queue topicQueue2 = new Queue(topicQueue2Name, false);

    TopicExchange topicExchange = new TopicExchange(topicExchangeName);

    return Arrays.asList(
      topicQueue1,
      topicQueue2,
      topicExchange,
      BindingBuilder
        .bind(topicQueue1)
        .to(topicExchange).with("*.important.*"),
      BindingBuilder
        .bind(topicQueue2)
        .to(topicExchange).with("#.error"));
}

With this configuration, we expect topicQueue1 to get all messages with routing keys having a three-word pattern with the middle word being “important” – for example: “user.important.error” or “blog.important.notification”.

The second queue will get all messages with routing keys ending in error; matching examples are “user.important.error” or “blog.post.save.error”.

4. Setting Up a Producer

We’ll need to create a message producer to use the exchanges we configured. The BroadcastMessageProducer class will use a RabbitTemplate and its method convertAndSend to send messages:

@Component
public class BroadcastMessageProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessages(String message) {
        rabbitTemplate.convertAndSend(
          SpringAmqpConfig.fanoutExchangeName, "", message);
        rabbitTemplate.convertAndSend(
          SpringAmqpConfig.topicExchangeName, "user.not-important.info", message);
        rabbitTemplate.convertAndSend(
          SpringAmqpConfig.topicExchangeName, "user.important.error", message);
    }
}

The RabbitTemplate provides many overloaded convertAndSend() methods for different exchange types.

When we send a message to fanout exchange, the routing key is ignored, and the message is passed to all bound queues.

When we send a message to the topic exchange, we need to pass a routing key. Based on this routing key the message will be delivered to specific queues.

5. Configuring Consumers

Finally, let’s set up four consumers – one for each queue – to pick up the messages produced:

@Component
public class BroadcastMessageConsumers {

    @RabbitListener(queues = {SpringAmqpConfig.fanoutQueue1Name})
    public void receiveMessageFromFanout1(String message) {
    }

    @RabbitListener(queues = {SpringAmqpConfig.fanoutQueue2Name})
    public void receiveMessageFromFanout2(String message) {
    }

    @RabbitListener(queues = {SpringAmqpConfig.topicQueue1Name})
    public void receiveMessageFromTopic1(String message) {
    }

    @RabbitListener(queues = {SpringAmqpConfig.topicQueue2Name})
    public void receiveMessageFromTopic2(String message) {
    }
}

We configure consumers using the @RabbitListener annotation. The only argument passed here is the queues’ name. Consumers are not aware here of exchanges or routing keys.

6. Running the Example

Our sample project is a Spring Boot application, and so it will initialize the application together with a connection to RabbitMQ and set up all queues, exchanges, and bindings.

By default, our application expects a RabbitMQ instance running on the localhost on port 5672. You can modify the defaults in application.yaml.

Our project exposes HTTP endpoint on the URI: “/broadcast”, that accepts POST calls with a message in the request body.

When we send a request to this URI with body “Test” we should see something similar to this in log output:

2017-04-14 12:27:59.611  INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers    : Received fanout 1 message:Test
2017-04-14 12:27:59.611  INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers    : Received topic 2 message: Test
2017-04-14 12:27:59.611  INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers    : Received fanout 2 message: Test
2017-04-14 12:27:59.611  INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers    : Received topic 1 message: Test
2017-04-14 12:27:59.612  INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers    : Received topic 2 message: Test

The order in which you will see these messages is, of course, not guaranteed.

7. Conclusion

In this quick tutorial, we covered fanout and topic exchanges with Spring AMQP and RabbitMQ.

The complete source code and all code snippets for this article are available on GitHub repository.

The Price of all “Learn Spring Security” course packages will permanently increase by $50 on the 8th of December:

>>> GET ACCESS NOW