Configuring the Broker

The AMQP specification describes how the protocol can be used to configure queues, exchanges, and bindings on the broker. These operations (which are portable from the 0.8 specification and higher) are present in the AmqpAdmin interface in the org.springframework.amqp.core package. The RabbitMQ implementation of that class is RabbitAdmin located in the org.springframework.amqp.rabbit.core package.

The AmqpAdmin interface is based on using the Spring AMQP domain abstractions and is shown in the following listing:

public interface AmqpAdmin {

    // Exchange Operations

    void declareExchange(Exchange exchange);

    void deleteExchange(String exchangeName);

    // Queue Operations

    Queue declareQueue();

    String declareQueue(Queue queue);

    void deleteQueue(String queueName);

    void deleteQueue(String queueName, boolean unused, boolean empty);

    void purgeQueue(String queueName, boolean noWait);

    // Binding Operations

    void declareBinding(Binding binding);

    void removeBinding(Binding binding);

    Properties getQueueProperties(String queueName);

}

See also Scoped Operations.

The getQueueProperties() method returns some limited information about the queue (message count and consumer count). The keys for the properties returned are available as constants in the RabbitTemplate (QUEUE_NAME, QUEUE_MESSAGE_COUNT, and QUEUE_CONSUMER_COUNT). The RabbitMQ REST API provides much more information in the QueueInfo object.

The no-arg declareQueue() method defines a queue on the broker with a name that is automatically generated. The additional properties of this auto-generated queue are exclusive=true, autoDelete=true, and durable=false.

The declareQueue(Queue queue) method takes a Queue object and returns the name of the declared queue. If the name property of the provided Queue is an empty String, the broker declares the queue with a generated name. That name is returned to the caller. That name is also added to the actualName property of the Queue. You can use this functionality programmatically only by invoking the RabbitAdmin directly. When using auto-declaration by the admin when defining a queue declaratively in the application context, you can set the name property to "" (the empty string). The broker then creates the name. Starting with version 2.1, listener containers can use queues of this type. See Containers and Broker-Named queues for more information.

This is in contrast to an AnonymousQueue where the framework generates a unique (UUID) name and sets durable to false and exclusive, autoDelete to true. A <rabbit:queue/> with an empty (or missing) name attribute always creates an AnonymousQueue.

See AnonymousQueue to understand why AnonymousQueue is preferred over broker-generated queue names as well as how to control the format of the name. Starting with version 2.1, anonymous queues are declared with argument Queue.X_QUEUE_LEADER_LOCATOR set to client-local by default. This ensures that the queue is declared on the node to which the application is connected. Declarative queues must have fixed names because they might be referenced elsewhere in the context — such as in the listener shown in the following example:

<rabbit:listener-container>
    <rabbit:listener ref="listener" queue-names="#{someQueue.name}" />
</rabbit:listener-container>

The RabbitMQ implementation of this interface is RabbitAdmin, which, when configured by using Spring XML, resembles the following example:

<rabbit:connection-factory id="connectionFactory"/>

<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory"/>

When the CachingConnectionFactory cache mode is CHANNEL (the default), the RabbitAdmin implementation does automatic lazy declaration of queues, exchanges, and bindings declared in the same ApplicationContext. These components are declared as soon as a Connection is opened to the broker. There are some namespace features that make this very convenient — for example, in the Stocks sample application, we have the following:

<rabbit:queue id="tradeQueue"/>

<rabbit:queue id="marketDataQueue"/>

<fanout-exchange name="broadcast.responses"
                 xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="tradeQueue"/>
    </bindings>
</fanout-exchange>

<topic-exchange name="app.stock.marketdata"
                xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="marketDataQueue" pattern="${stocks.quote.pattern}"/>
    </bindings>
</topic-exchange>

In the preceding example, we use anonymous queues (actually, internally, just queues with names generated by the framework, not by the broker) and refer to them by ID. We can also declare queues with explicit names, which also serve as identifiers for their bean definitions in the context. The following example configures a queue with an explicit name:

<rabbit:queue name="stocks.trade.queue"/>
You can provide both id and name attributes. This lets you refer to the queue (for example, in a binding) by an ID that is independent of the queue name. It also allows standard Spring features (such as property placeholders and SpEL expressions for the queue name). These features are not available when you use the name as the bean identifier.

Queues can be configured with additional arguments — for example, x-message-ttl. When you use the namespace support, they are provided in the form of a Map of argument-name/argument-value pairs, which are defined by using the <rabbit:queue-arguments> element. The following example shows how to do so:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>

By default, the arguments are assumed to be strings. For arguments of other types, you must provide the type. The following example shows how to specify the type:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments value-type="java.lang.Long">
        <entry key="x-message-ttl" value="100"/>
    </rabbit:queue-arguments>
</rabbit:queue>

When providing arguments of mixed types, you must provide the type for each entry element. The following example shows how to do so:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-message-ttl">
            <value type="java.lang.Long">100</value>
        </entry>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>

With Spring Framework 3.2 and later, this can be declared a little more succinctly, as follows:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-message-ttl" value="100" value-type="java.lang.Long"/>
        <entry key="x-ha-policy" value="all"/>
    </rabbit:queue-arguments>
</rabbit:queue>

When you use Java configuration, the Queue.X_QUEUE_LEADER_LOCATOR argument is supported as a first class property through the setLeaderLocator() method on the Queue class. Starting with version 2.1, anonymous queues are declared with this property set to client-local by default. This ensures that the queue is declared on the node the application is connected to.

The RabbitMQ broker does not allow declaration of a queue with mismatched arguments. For example, if a queue already exists with no time to live argument, and you attempt to declare it with (for example) key="x-message-ttl" value="100", an exception is thrown.

By default, the RabbitAdmin immediately stops processing all declarations when any exception occurs. This could cause downstream issues, such as a listener container failing to initialize because another queue (defined after the one in error) is not declared.

This behavior can be modified by setting the ignore-declaration-exceptions attribute to true on the RabbitAdmin instance. This option instructs the RabbitAdmin to log the exception and continue declaring other elements. When configuring the RabbitAdmin using Java, this property is called ignoreDeclarationExceptions. This is a global setting that applies to all elements. Queues, exchanges, and bindings have a similar property that applies to just those elements.

Prior to version 1.6, this property took effect only if an IOException occurred on the channel, such as when there is a mismatch between current and desired properties. Now, this property takes effect on any exception, including TimeoutException and others.

In addition, any declaration exceptions result in the publishing of a DeclarationExceptionEvent, which is an ApplicationEvent that can be consumed by any ApplicationListener in the context. The event contains a reference to the admin, the element that was being declared, and the Throwable.

Headers Exchange

Starting with version 1.3, you can configure the HeadersExchange to match on multiple headers. You can also specify whether any or all headers must match. The following example shows how to do so:

<rabbit:headers-exchange name="headers-test">
    <rabbit:bindings>
        <rabbit:binding queue="bucket">
            <rabbit:binding-arguments>
                <entry key="foo" value="bar"/>
                <entry key="baz" value="qux"/>
                <entry key="x-match" value="all"/>
            </rabbit:binding-arguments>
        </rabbit:binding>
    </rabbit:bindings>
</rabbit:headers-exchange>

Starting with version 1.6, you can configure Exchanges with an internal flag (defaults to false) and such an Exchange is properly configured on the Broker through a RabbitAdmin (if one is present in the application context). If the internal flag is true for an exchange, RabbitMQ does not let clients use the exchange. This is useful for a dead letter exchange or exchange-to-exchange binding, where you do not wish the exchange to be used directly by publishers.

To see how to use Java to configure the AMQP infrastructure, look at the Stock sample application, where there is the @Configuration class AbstractStockRabbitConfiguration, which ,in turn has RabbitClientConfiguration and RabbitServerConfiguration subclasses. The following listing shows the code for AbstractStockRabbitConfiguration:

@Configuration
public abstract class AbstractStockAppRabbitConfiguration {

    @Bean
    public CachingConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }

    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public TopicExchange marketDataExchange() {
        return new TopicExchange("app.stock.marketdata");
    }

    // additional code omitted for brevity

}

In the Stock application, the server is configured by using the following @Configuration class:

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

    @Bean
    public Queue stockRequestQueue() {
        return new Queue("app.stock.request");
    }
}

This is the end of the whole inheritance chain of @Configuration classes. The end result is that TopicExchange and Queue are declared to the broker upon application startup. There is no binding of TopicExchange to a queue in the server configuration, as that is done in the client application. The stock request queue, however, is automatically bound to the AMQP default exchange. This behavior is defined by the specification.

The client @Configuration class is a little more interesting. Its declaration follows:

@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {

    @Value("${stocks.quote.pattern}")
    private String marketDataRoutingKey;

    @Bean
    public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }

    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */
    @Bean
    public Binding marketDataBinding() {
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }

    // additional code omitted for brevity

}

The client declares another queue through the declareQueue() method on the AmqpAdmin. It binds that queue to the market data exchange with a routing pattern that is externalized in a properties file.

Builder API for Queues and Exchanges

Version 1.6 introduces a convenient fluent API for configuring Queue and Exchange objects when using Java configuration. The following example shows how to use it:

@Bean
public Queue queue() {
    return QueueBuilder.nonDurable("foo")
        .autoDelete()
        .exclusive()
        .withArgument("foo", "bar")
        .build();
}

@Bean
public Exchange exchange() {
  return ExchangeBuilder.directExchange("foo")
      .autoDelete()
      .internal()
      .withArgument("foo", "bar")
      .build();
}

Starting with version 2.0, the ExchangeBuilder now creates durable exchanges by default, to be consistent with the simple constructors on the individual AbstractExchange classes. To make a non-durable exchange with the builder, use .durable(false) before invoking .build(). The durable() method with no parameter is no longer provided.

Version 2.2 introduced fluent APIs to add "well known" exchange and queue arguments…​

@Bean
public Queue allArgs1() {
    return QueueBuilder.nonDurable("all.args.1")
            .ttl(1000)
            .expires(200_000)
            .maxLength(42)
            .maxLengthBytes(10_000)
            .overflow(Overflow.rejectPublish)
            .deadLetterExchange("dlx")
            .deadLetterRoutingKey("dlrk")
            .maxPriority(4)
            .lazy()
            .leaderLocator(LeaderLocator.minLeaders)
            .singleActiveConsumer()
            .build();
}

@Bean
public DirectExchange ex() {
    return ExchangeBuilder.directExchange("ex.with.alternate")
            .durable(true)
            .alternate("alternate")
            .build();
}

Declaring Collections of Exchanges, Queues, and Bindings

You can wrap collections of Declarable objects (Queue, Exchange, and Binding) in Declarables objects. The RabbitAdmin detects such beans (as well as discrete Declarable beans) in the application context, and declares the contained objects on the broker whenever a connection is established (initially and after a connection failure). The following example shows how to do so:

@Configuration
public static class Config {

    @Bean
    public CachingConnectionFactory cf() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    public RabbitAdmin admin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }

    @Bean
    public DirectExchange e1() {
        return new DirectExchange("e1", false, true);
    }

    @Bean
    public Queue q1() {
        return new Queue("q1", false, false, true);
    }

    @Bean
    public Binding b1() {
        return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
    public Declarables es() {
        return new Declarables(
                new DirectExchange("e2", false, true),
                new DirectExchange("e3", false, true));
    }

    @Bean
    public Declarables qs() {
        return new Declarables(
                new Queue("q2", false, false, true),
                new Queue("q3", false, false, true));
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Declarables prototypes() {
        return new Declarables(new Queue(this.prototypeQueueName, false, false, true));
    }

    @Bean
    public Declarables bs() {
        return new Declarables(
                new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
                new Binding("q3", DestinationType.QUEUE, "e3", "k3", null));
    }

    @Bean
    public Declarables ds() {
        return new Declarables(
                new DirectExchange("e4", false, true),
                new Queue("q4", false, false, true),
                new Binding("q4", DestinationType.QUEUE, "e4", "k4", null));
    }

}
In versions prior to 2.1, you could declare multiple Declarable instances by defining beans of type Collection<Declarable>. This can cause undesirable side effects in some cases, because the admin has to iterate over all Collection<?> beans.

Version 2.2 added the getDeclarablesByType method to Declarables; this can be used as a convenience, for example, when declaring the listener container bean(s).

public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
        Declarables mixedDeclarables, MessageListener listener) {

    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0]));
    container.setMessageListener(listener);
    return container;
}

Conditional Declaration

By default, all queues, exchanges, and bindings are declared by all RabbitAdmin instances (assuming they have auto-startup="true") in the application context.

Starting with version 2.1.9, the RabbitAdmin has a new property explicitDeclarationsOnly (which is false by default); when this is set to true, the admin will only declare beans that are explicitly configured to be declared by that admin.

Starting with the 1.2 release, you can conditionally declare these elements. This is particularly useful when an application connects to multiple brokers and needs to specify with which brokers a particular element should be declared.

The classes representing these elements implement Declarable, which has two methods: shouldDeclare() and getDeclaringAdmins(). The RabbitAdmin uses these methods to determine whether a particular instance should actually process the declarations on its Connection.

The properties are available as attributes in the namespace, as shown in the following examples:

<rabbit:admin id="admin1" connection-factory="CF1" />

<rabbit:admin id="admin2" connection-factory="CF2" />

<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />

<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />

<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />

<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />

<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />

<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
    <rabbit:bindings>
        <rabbit:binding key="foo" queue="bar"/>
    </rabbit:bindings>
</rabbit:direct-exchange>
By default, the auto-declare attribute is true and, if the declared-by is not supplied (or is empty), then all RabbitAdmin instances declare the object (as long as the admin’s auto-startup attribute is true, the default, and the admin’s explicit-declarations-only attribute is false).

Similarly, you can use Java-based @Configuration to achieve the same effect. In the following example, the components are declared by admin1 but not by admin2:

@Bean
public RabbitAdmin admin1() {
    return new RabbitAdmin(cf1());
}

@Bean
public RabbitAdmin admin2() {
    return new RabbitAdmin(cf2());
}

@Bean
public Queue queue() {
    Queue queue = new Queue("foo");
    queue.setAdminsThatShouldDeclare(admin1());
    return queue;
}

@Bean
public Exchange exchange() {
    DirectExchange exchange = new DirectExchange("bar");
    exchange.setAdminsThatShouldDeclare(admin1());
    return exchange;
}

@Bean
public Binding binding() {
    Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
    binding.setAdminsThatShouldDeclare(admin1());
    return binding;
}

A Note On the id and name Attributes

The name attribute on <rabbit:queue/> and <rabbit:exchange/> elements reflects the name of the entity in the broker. For queues, if the name is omitted, an anonymous queue is created (see AnonymousQueue).

In versions prior to 2.0, the name was also registered as a bean name alias (similar to name on <bean/> elements).

This caused two problems:

  • It prevented the declaration of a queue and exchange with the same name.

  • The alias was not resolved if it contained a SpEL expression (#{…​}).

Starting with version 2.0, if you declare one of these elements with both an id and a name attribute, the name is no longer declared as a bean name alias. If you wish to declare a queue and exchange with the same name, you must provide an id.

There is no change if the element has only a name attribute. The bean can still be referenced by the name — for example, in binding declarations. However, you still cannot reference it if the name contains SpEL — you must provide an id for reference purposes.

AnonymousQueue

In general, when you need a uniquely-named, exclusive, auto-delete queue, we recommend that you use the AnonymousQueue instead of broker-defined queue names (using "" as a Queue name causes the broker to generate the queue name).

This is because:

  1. The queues are actually declared when the connection to the broker is established. This is long after the beans are created and wired together. Beans that use the queue need to know its name. In fact, the broker might not even be running when the application is started.

  2. If the connection to the broker is lost for some reason, the admin re-declares the AnonymousQueue with the same name. If we used broker-declared queues, the queue name would change.

You can control the format of the queue name used by AnonymousQueue instances.

By default, the queue name is prefixed by spring.gen- followed by a base64 representation of the UUID — for example: spring.gen-MRBv9sqISkuCiPfOYfpo4g.

You can provide an AnonymousQueue.NamingStrategy implementation in a constructor argument. The following example shows how to do so:

@Bean
public Queue anon1() {
    return new AnonymousQueue();
}

@Bean
public Queue anon2() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-"));
}

@Bean
public Queue anon3() {
    return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT);
}

The first bean generates a queue name prefixed by spring.gen- followed by a base64 representation of the UUID — for example: spring.gen-MRBv9sqISkuCiPfOYfpo4g. The second bean generates a queue name prefixed by something- followed by a base64 representation of the UUID. The third bean generates a name by using only the UUID (no base64 conversion) — for example, f20c818a-006b-4416-bf91-643590fedb0e.

The base64 encoding uses the “URL and Filename Safe Alphabet” from RFC 4648. Trailing padding characters (=) are removed.

You can provide your own naming strategy, whereby you can include other information (such as the application name or client host) in the queue name.

You can specify the naming strategy when you use XML configuration. The naming-strategy attribute is present on the <rabbit:queue> element for a bean reference that implements AnonymousQueue.NamingStrategy. The following examples show how to specify the naming strategy in various ways:

<rabbit:queue id="uuidAnon" />

<rabbit:queue id="springAnon" naming-strategy="uuidNamer" />

<rabbit:queue id="customAnon" naming-strategy="customNamer" />

<bean id="uuidNamer" class="org.springframework.amqp.core.AnonymousQueue.UUIDNamingStrategy" />

<bean id="customNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy">
    <constructor-arg value="custom.gen-" />
</bean>

The first example creates names such as spring.gen-MRBv9sqISkuCiPfOYfpo4g. The second example creates names with a String representation of a UUID. The third example creates names such as custom.gen-MRBv9sqISkuCiPfOYfpo4g.

You can also provide your own naming strategy bean.

Starting with version 2.1, anonymous queues are declared with argument Queue.X_QUEUE_LEADER_LOCATOR set to client-local by default. This ensures that the queue is declared on the node to which the application is connected. You can revert to the previous behavior by calling queue.setLeaderLocator(null) after constructing the instance.

Recovering Auto-Delete Declarations

Normally, the RabbitAdmin (s) only recover queues/exchanges/bindings that are declared as beans in the application context; if any such declarations are auto-delete, they will be removed by the broker if the connection is lost. When the connection is re-established, the admin will redeclare the entities. Normally, entities created by calling admin.declareQueue(…​), admin.declareExchange(…​) and admin.declareBinding(…​) will not be recovered.

Starting with version 2.4, the admin has a new property redeclareManualDeclarations; when true, the admin will recover these entities in addition to the beans in the application context.

Recovery of individual declarations will not be performed if deleteQueue(…​), deleteExchange(…​) or removeBinding(…​) is called. Associated bindings are removed from the recoverable entities when queues and exchanges are deleted.

Finally, calling resetAllManualDeclarations() will prevent the recovery of any previously declared entities.