Search code examples
javaspring-bootrabbitmqspring-amqpspring-rabbit

Spring AMQP @RabbitListener is not ready to receive messages on @ApplicationReadyEvent. Queues/Bindings declared too slow?


we have a larger multi service java spring app that declares about 100 exchanges and queues in RabbitMQ on startup. Some are declared explicitly via Beans, but most of them are declared implicitly via @RabbitListener Annotations.

@Component
@RabbitListener(
    bindings = @QueueBinding(key = {"example.routingkey"},
        exchange = @Exchange(value = "example.exchange", type = ExchangeTypes.TOPIC),
        value = @Queue(name = "example_queue", autoDelete = "true", exclusive = "true")))
public class ExampleListener{

  @RabbitHandler
  public void handleRequest(final ExampleRequest request) {
    System.out.println("got request!");
  }

There are quite a lot of these listeners in the whole application.

The services of the application sometimes talk to each other via RabbitMq, so take a example Publisher that publishes a message to the Example Exchange that the above ExampleListener is bound to. If that publish happens too early in the application lifecycle (but AFTER all the Spring Lifecycle Events are through, so after ApplicationReadyEvent, ContextStartedEvent), the binding of the Example Queue to the Example Exchange has not yet happend and the very first publish and reply chain will fail. In other words, the above Example Listener would not print "got request".

We "fixed" this problem by simply waiting 3 seconds before we start sending any RabbitMq messages to give it time to declare all queues,exchanges and bindings but this seems like a very suboptimal solution.

Does anyone else have some advice on how to fix this problem? It is quite hard to recreate as I would guess that it only occurs with a large amount of queues/exchanges/bindings that RabbitMq can not create fast enough. Forcing Spring to synchronize this creation process and wait for a confirmation by RabbitMq would probably fix this but as I see it, there is no built in way to do this.


Solution

  • Are you using multiple connection factories?

    Or are you setting usePublisherConnection on the RabbitTemplate? (which is recommended, especially for a complex application like yours).

    Normally, a single connection is used and all users of it will block until the admin has declared all the elements (it is run as a connection listener).

    If the template is using a different connection factory, it will not block because a different connection is used.

    If that is the case, and you are using the CachingConnectionFactory, you can call createConnection().close() on the consumer connection factory during initialization, before sending any messages. That call will block until all the declarations are done.