Version / environment
• Axon Framework 5.4.1 (axon-spring-boot-starter, axon-spring, axon-messaging)
• Spring Boot 4 / Spring Framework 7, Java 25
• Pooled streaming processor, initial-segment-count=1, JPA event storage (MySQL)
Summary
Several @ComponentComponent be@EventHandlerns with @EventHandler methods belong to the same proce@Namespacesor, either through @Namespace or through package-based grou@Ordering. They are annotated with @Order. The order inside the processor is not@Orderdeterministic and does not
follow @Order.
The Javadoc of MessageHandlerLookup#sortByOrder says th@Order handler bean references are sorted by @Order. The sorted list is later collected into a Set and grouped per processor. The sort order is therefore discarded before the processor modules
are built.
Expected behaviour
For two handler beans in the same processor, both hand@Ordering the same event, the bean with the lower @OrderOrder value handles the event first. This is how @Order behaved for event handlers in Axon 4.
Actual behaviour
The handling order across the beans is effectively arb@Ordertrary. Depending on the run, the bean with the higher @Order value handles the event first.
Root cause (from the 5.4.1 sources)
- org.axonframework.extension.spring.config.MessageHandlerLo@Orderkup#sortByOrder sorts the discovered handler bean names by @Order (via OrderUtils.getOrder(…, Ordered.LOWEST_PRECEDENCE)) and passes the sorted List on.
- org.axonframework.extension.spring.config.MessageHandlerConfigurer#configureEventHandlers maps those names to SimpleEventHandlerDescriptor objects and collects them with Collectors.toSet():
Set<EventProcessorDefinition.EventHandlerDescriptor> handlers =
handlerBeansRefs.stream()
.map(name → new SimpleEventHandlerDescriptor(name, beanFactory))
.collect(Collectors.toSet());
toSet() makes no ordering guarantee, so the sorted order is lost here. - DefaultProcessorModuleFactory#buildProcessorModules(Set<…>) groups the set per processor name. The order of the handlers inside a processor follows the iteration order of that set.
- ProcessorEventHandlingComponents then invokes the components in list order, per event, component after component. If the order were preserved up to this point, it would be respected at runtime.
Steps to reproduce
- Create a Spring Boot application with Axon 5.4.1, initial@Componentsegment-count=1 and a pooled stream@Namespaceng processor.
- Create two @Component@Orderbeans in the same processor (same @Namespace)@Orderthat both handle event E:
- A with @Order(1): writes row X to the database.
- B with @Order(2): reads row X and expects it to exist.
- Publish E and run the test repeatedly.
Observed in our project
We have a read model with five listeners in one processor. Two of them depend on each other for the same event: the time is stored by one listener, @Ordernd the enrollments @Orderre then updat@Orderd by another listener that reads that time. The first has
@Order(1) and the second @Order(5).
• With @Order and 1 segment, EnrollmentIT failed in 3 to 5 of about 10 runs, in two variants:
• the dependent row did not exist yet when the second handler ran;
• a stale value was read, for example ex@Orderected 00:09:03.45 but was 00:10:03.45.
• When we call the first handler from within the second handler, so @Order is@Orderno longer needed, EnrollmentIT passed 10 of 10 runs.
Impact
Applications that migrated from Axon 4 and rely on @Order to sequence listeners in one processor get intermittent, hard-to-diagnose failures. The failures only show up as flaky tests or inconsistent read models.
Suggested fix
Preserve the sort order in MessageHandlerConfigurer#configureEventHandlers, for example by collecting into a LinkedHashSet or by passing a List on. Make sure DefaultProcessorModuleFactory#buildProcessorModules and its groupingBy keep that order
inside each processor, for example with a LinkedHashMap and list-based grouping.
Not verified
• I have not built a minimal standalone reproducer project. The evidence comes from our application and from reading the sources.
• I have not checked whether other Axon versions (5.0.x to 5.3.x) behave the same.
• I only checked event handlers. I did not look at command or query handlers.
Workaround
Do not rely on @Order. Call the dependent handler explicitly from the other handler, or put the logic in one handler.