TL;DR

  • Scenario: Need to route messages by “multi-dimensional tags” (e.g., source+level/region+type)
  • Conclusion: Topic implements fine-grained distribution through routingKey (dot-separated words) and bindingKey (* /#) pattern matching. Unmatched messages are directly discarded

RabbitMQ Topic Mode

When using topic type exchange, queues bind to exchange via bindingKey, where bindingKey can contain wildcards for flexible matching. When routing messages, exchange performs pattern matching between message’s routingKey and queue’s bindingKey, achieving finer-grained message distribution.

Wildcard Rules

  1. * (asterisk): Matches exactly one word
  2. # (hash): Matches zero or more words

Application Scenario Examples

Assuming the following requirements:

  1. Need to receive all error level logs from cron service
  2. Need to receive all level logs from kern service
  3. Need to receive critical level logs from all services

Corresponding binding relationships:

  • Queue1 binding key: cron.error
  • Queue2 binding key: kern.*
  • Queue3 binding key: *.critical

Message routingKey matching results:

  • cron.error → Queue1
  • kern.info → Queue2
  • auth.critical → Queue3

RoutingKey Format Specification

  1. Format Requirements:

    • Must use dotted-word notation
    • Each word separated by dot (.)
    • Total string length must not exceed 255 bytes
  2. Naming Examples:

    • Finance domain: stock.usd.nyse
    • Automotive industry: nyse.vwm
    • General example: quick.orange.rabbit

Binding Matching Rules Detail

Q1 binds to *.orange.*

  • Matches: quick.orange.fox, lazy.orange.elephant
  • Does not match: orange.quick.fox (first word not wildcard), quick.orange (missing third word)

Q2 binds to *.*.rabbit and lazy.#

  • *.*.rabbit: Matches quick.orange.rabbit, lazy.brown.rabbit
  • lazy.#: Matches lazy, lazy.orange, lazy.orange.male.rabbit

When No Match

If message’s routingKey doesn’t match any binding (e.g., quick.brown.fox), that message is directly discarded and won’t enter any queue.


Topic Exchange Behavior Analogy

  • Similarity to Fanout Exchange: If bindingKey uses # (e.g., # or lazy.#), exchange broadcasts all messages to matching queues.
  • Similarity to Direct Exchange: If bindingKey doesn’t use * or # at all (e.g., orange.rabbit), exchange exactly matches routingKey.

Example Scenarios

routingKeyMatching Queues
quick.orange.rabbitQ2
lazy.orange.elephantQ1 and Q2
orange.rabbitNo match, discarded
lazy.brown.rabbitQ2

Java Code Examples

EmitLogTopic (Producer)

package icu.wzk;

public class EmitLogTopic {
    private static final String EXCHANGE_NAME = "topic_logs";
    private static final String[] SPEED = {"lazy", "quick", "normal"};
    private static final String[] COLOR = {"black", "orange", "red", "yellow", "blue", "white", "pink"};
    private static final String[] SPECIES = {"dog", "rabbit", "chicken", "horse", "bear", "cat"};
    private static final Random RANDOM = new Random();

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1) Configure connection
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        factory.setUsername("admin");
        factory.setPassword("secret");
        factory.setPort(5672);

        // 2) Establish connection and Channel
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 3) Declare topic exchange
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

            // 4) Loop to send 10 random messages
            for (int i = 0; i < 10; i++) {
                String speed = pick(SPEED);
                String color = pick(COLOR);
                String species = pick(SPECIES);

                String message = speed + "-" + color + "-" + species;
                String routingKey = speed + "." + color + "." + species;

                channel.basicPublish(
                        EXCHANGE_NAME,
                        routingKey,
                        null,
                        message.getBytes(StandardCharsets.UTF_8)
                );

                System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
            }
        }
    }

    private static String pick(String[] arr) {
        return arr[RANDOM.nextInt(arr.length)];
    }
}

ReceiveLogsTopic (Consumer)

package icu.wzk;

public class ReceiveLogsTopic {
    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("admin");
        factory.setPassword("secret");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        String queueName = channel.queueDeclare().getQueue();

        String bindingKey = "*.*.rabbit";
        channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);

        DeliverCallback callback = (consumerTag, delivery) -> {
            String body = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(bindingKey + " matched message: " + body);
        };

        boolean autoAck = true;
        channel.basicConsume(queueName, autoAck, callback, consumerTag -> {});
    }
}

Error Quick Reference

SymptomRoot CauseFix
Producer sends successfully, consumer can’t receiveroutingKey and bindingKey have no matchUnify routingKey naming conventions
NOT_FOUND - no exchangeExchange doesn’t exist or vhost inconsistentFirst exchangeDeclare
PRECONDITION_FAILEDSame name exchange type differentUse new exchange name
Messages lost after consumer restartautoAck=true, crash before processingSet autoAck=false, basicAck after success
Wildcard in middle of word doesn’t workWildcard must be independent wordChange to dot-separated word structure
Consumer temporary queue disappearsexclusive/auto-deleteExplicitly declare fixed queue name + durable

Notes

  • Wildcards must appear as independent words, cannot be embedded within words
  • Matching is case-sensitive
  • Empty string cannot be used as routingKey
  • Dots cannot appear at beginning or end