I am currently working on a Spring Boot application using Axon and have encountered an issue while implementing both retry on event processing failure and a Dead Letter Queue (DLQ).
In my configuration, I have registered two beans:
- registerListenerInvocationErrorHandler – To handle errors and retry event processing.
- registerDeadLetterQueueOnEventProcessingFailure – To store failed events in a DLQ after errors occur.
Issue:
When I include both configurations, the ListenerInvocationErrorHandler does not seem to work, meaning that my retry logic is not triggered before the event is stored in the DLQ.
However, if I remove the registerDeadLetterQueue bean, the ListenerInvocationErrorHandler works as expected.
Expected Behavior:
I would like to retry event processing up to 3 times before moving the event to the Dead Letter Queue if the error persists.
code:
@Bean
public ConfigurerModule eventProcessingForRegisterListenerInvocationErrorHandler(MongoTemplate mongoTemplate, RetryListenerInvocationErrorHandler retryListenerInvocationErrorHandler) {
return configurer -> configurer.eventProcessing(
processingConfigurer -> processingConfigurer
.registerListenerInvocationErrorHandler(
"customToolEventProcessor",
conf -> retryListenerInvocationErrorHandler
)
);
}
@Bean
public ConfigurerModule registerDeadLetterQueueOnEventProcessingFailure(MongoTemplate mongoTemplate, RetryListenerInvocationErrorHandler retryListenerInvocationErrorHandler) {
return configurer -> configurer.eventProcessing(
processingConfigurer -> processingConfigurer
// Configure Dead Letter Queue for unrecoverable errors
.registerDeadLetterQueue(
"customToolEventProcessor",
config -> MongoSequencedDeadLetterQueue.builder()
.processingGroup("customToolEventProcessor")
.maxSequences(256)
.maxSequenceSize(256)
.mongoTemplate(mongoTemplate)
.transactionManager(config.getComponent(TransactionManager.class))
.serializer(config.serializer())
.build()
)
);
}
@Component
public class RetryListenerInvocationErrorHandler implements ListenerInvocationErrorHandler {
private static final int MAX_RETRIES = 3;
@Override
public void onError(@Nonnull Exception exception,
@Nonnull EventMessage<?> event,
@Nonnull EventMessageHandler eventHandler) throws Exception {
UnitOfWork<?> unitOfWork = CurrentUnitOfWork.get();
int retryCount = unitOfWork.getOrComputeResource("retryCount", k -> 0);
if (retryCount < MAX_RETRIES) {
unitOfWork.resources().put("retryCount", retryCount + 1);
// Log the retry attempt
System.out.println("Retrying event handling, attempt " + (retryCount + 1));
throw exception; // Rethrow to trigger retry
} else {
// Log the failure
System.out.println("Max retries reached for event: " + event.getPayloadType().getSimpleName());
// Optionally, you can handle the event differently here, like sending it to a DLQ
throw exception; // Rethrow to propagate the failure
}
}
}
Question:
Is there a recommended way to ensure that the retry mechanism is attempted before an event is stored in the DLQ? How can I configure Axon to first retry processing (e.g., 3 attempts) before sending the event to DLQ when all retries fail?
I would appreciate any insights or guidance on this issue.