Search code examples
javaspringspring-bootrabbitmqspring-rabbit

Spring RabbitTemplate setRetryTemplate & setRecoveryCallback ignored


Question: After a fixed amount of retries I would like to send the message to an error queue and consume it from its original queue. I want to find a generic solution because I handle a lot of different messages and depending on the exception they raise I want to act differently. How can I set the RecoveryCallback so that Spring triggers it after maxRetries?

What I currently do
I try to set a RetryTemplate and a RecoveryCallback. When I run the application and publish a message to the test queue I expect the processing in EListener#receive to fail 3 times and then trigger my configured RecoveryCallback which then routes the message based on the context to a specific error queue.

What actually happens What actually happens is that Spring Boot initializes the RabbitAdmin object with its own RabbitTemplate and therefore does not use my configured RabbitTemplate bean.

I have the following directory structure:

rabbit
  |___ EListener.java
  |___ Rabbit.java
test
  |___ Test.java

I have the following code in Rabbit.java

@Configuration
public class Rabbit {

    @Autowired
    ConnectionFactory connectionFactory;

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setRetryTemplate(createRetryTemplate());
        rabbitTemplate.setRecoveryCallback(createRecoveryCallback());
        return rabbitTemplate;
    }

    createRecoveryCallback() // Omitted for brevity
    createRetryTemplate() // Omitted for brevity
}

The EListener.java file contains:

@Component
public class EListener {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "test", durable = "true"), exchange = @Exchange(value = "test", type = ExchangeTypes.TOPIC, durable = "true", autoDelete = "true"), key = "test"))
    public void receive(Message m) throws Exception {
        System.out.println(m);
        throw new Exception();
    }
}

Test.java contains:

@SpringBootApplication
@ComponentScan("rabbit")
public class Test {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Test.class).application().run(args);
    }
}

Solution

  • Adding a RetryTemplate to the RabbitTemplate is to retry publishing messages.

    To add retry on the consumer side, you have to add a retry interceptor to the listener container's advice chain.

    Since you are using @RabbitListener, the advice chain goes on the listener container factory @Bean, which means you'll have to declare one yourself instead of relying on the default one created by boot.

    Stateless retry does the retries in-memory; statefull retry requeues the message; it requires a messageId property (or some other mechanism to uniquely identify messages).