Interceptor Migration
Axon Framework 5 introduces significant changes to how interceptors work, reflecting the broader shift to async-first APIs and the move away from ThreadLocal-based patterns.
This path covers the migration of message interceptors from Axon Framework 4 to Axon Framework 5. For a complete understanding of how interceptors work in Axon Framework 5, see Message Intercepting.
|
The most significant changes are:
While the interfaces have changed, the fundamental concepts and use cases remain the same. |
MessageDispatchInterceptor interface changes
Dispatch interceptors are invoked when a message is dispatched/published on a bus, eventually even before a ProcessingContext is created.
-
Axon Framework 4
-
Axon Framework 5
public class MyDispatchInterceptor implements MessageDispatchInterceptor<CommandMessage<?>> {
@Override
public BiFunction<Integer, CommandMessage<?>, CommandMessage<?>> handle(
List<? extends CommandMessage<?>> messages) {
return (index, message) -> {
// Modify or enrich message
return message.andMetaData(Collections.singletonMap("dispatchTime", Instant.now()));
};
}
}
public class MyDispatchInterceptor implements MessageDispatchInterceptor<CommandMessage> {
@Override
public MessageStream<?> interceptOnDispatch(CommandMessage message,
@Nullable ProcessingContext context,
MessageDispatchInterceptorChain<CommandMessage> chain) {
// Modify or enrich message
CommandMessage enrichedMessage = message.andMetaData(
Collections.singletonMap("dispatchTime", Instant.now())
);
// Continue chain with modified message
return chain.proceed(enrichedMessage, context);
}
}
Key changes:
-
Method name:
handle()→interceptOnDispatch() -
Single message: Processes one message at a time instead of batching
-
Return type:
MessageStream<?>for async-first design -
ProcessingContext:
@Nullable- may not exist during dispatch -
Chain proceed: Must pass (possibly modified) message to continue
|
The If dispatching from within a handler (where context exists), pass it along to propagate correlation data. If dispatching from outside a handler (for example, HTTP endpoint), context will be |
MessageHandlerInterceptor interface changes
The MessageHandlerInterceptor interface has been redesigned to support async-first processing and explicit context management.
-
Axon Framework 4
-
Axon Framework 5
public class MyHandlerInterceptor implements MessageHandlerInterceptor<CommandMessage<?>> {
@Override
public Object handle(UnitOfWork<? extends CommandMessage<?>> unitOfWork,
InterceptorChain interceptorChain) throws Exception {
// Pre-processing
unitOfWork.onCommit(uow -> {
// Post-commit logic
});
// Continue chain synchronously
return interceptorChain.proceed();
}
}
Key characteristics:
-
UnitOfWorkprovided via ThreadLocal (CurrentUnitOfWork.get()) -
Synchronous
proceed()method -
Direct return of handler result
public class MyHandlerInterceptor implements MessageHandlerInterceptor<CommandMessage> {
@Override
public MessageStream<?> interceptOnHandle(CommandMessage message,
ProcessingContext context,
MessageHandlerInterceptorChain<CommandMessage> chain) {
// Pre-processing
context.runOnAfterCommit(ctx -> {
// Post-commit logic
});
// Continue chain - returns MessageStream
return chain.proceed(message, context);
}
}
Key changes:
-
Method name:
handle()→interceptOnHandle() -
Parameters: Receives
messageandcontextexplicitly -
Return type:
MessageStream<?>instead ofObject -
Chain proceed: Must pass
messageandcontexttochain.proceed() -
ProcessingContext: Mandatory parameter, never null during handling
The
ProcessingContextprovides lifecycle hooks similar toUnitOfWork: -
runOnPreInvocation(Consumer<ProcessingContext>)- Execute when context starts -
runOnAfterCommit(Consumer<ProcessingContext>)- Execute after successful commit -
onError(ProcessingErrorHandler)- Handle errors during processing
Declarative interceptor registration
-
Axon Framework 4
-
Axon Framework 5
Configurer configurer = DefaultConfigurer.defaultConfiguration();
configurer.registerCommandHandlerInterceptor(
config -> new MyCommandHandlerInterceptor()
);
configurer.registerDispatchInterceptor(
config -> new MyCommandDispatchInterceptor()
);
MessagingConfigurer configurer = MessagingConfigurer.create();
// Register handler interceptor for commands
configurer.registerCommandHandlerInterceptor(
config -> new MyCommandHandlerInterceptor()
);
// Register dispatch interceptor for commands
configurer.registerCommandDispatchInterceptor(
config -> new MyCommandDispatchInterceptor()
);
The following registration methods are available:
Handler Interceptors:
-
registerMessageHandlerInterceptor(ComponentBuilder)- All message types -
registerCommandHandlerInterceptor(ComponentBuilder)- Commands only -
registerEventHandlerInterceptor(ComponentBuilder)- Events only -
registerQueryHandlerInterceptor(ComponentBuilder)- Queries only
Dispatch Interceptors:
-
registerDispatchInterceptor(ComponentBuilder)- All message types -
registerCommandDispatchInterceptor(ComponentBuilder)- Commands only -
registerEventDispatchInterceptor(ComponentBuilder)- Events only -
registerQueryDispatchInterceptor(ComponentBuilder)- Queries only
MessagingConfigurer configurer = MessagingConfigurer.create();
// Register for multiple message types
configurer.registerCommandHandlerInterceptor(config -> new LoggingInterceptor("Command"))
.registerEventHandlerInterceptor(config -> new LoggingInterceptor("Event"))
.registerQueryHandlerInterceptor(config -> new LoggingInterceptor("Query"));
Configuration configuration = configurer.build();
Spring Boot interceptor registration
Spring Boot auto-configuration automatically discovers and registers interceptors declared as Spring beans.
-
Axon Framework 4
-
Axon Framework 5
@Component
public class MyCommandHandlerInterceptor implements MessageHandlerInterceptor<CommandMessage<?>> {
// Implementation
}
@Component
public class MyCommandHandlerInterceptor implements MessageHandlerInterceptor<CommandMessage> {
@Override
public MessageStream<?> interceptOnHandle(CommandMessage message,
ProcessingContext context,
MessageHandlerInterceptorChain<CommandMessage> chain) {
logger.info("Handling command: {}", message.type().name());
return chain.proceed(message, context);
}
}
@Component
public class MyCommandDispatchInterceptor implements MessageDispatchInterceptor<CommandMessage> {
@Override
public MessageStream<?> interceptOnDispatch(CommandMessage message,
@Nullable ProcessingContext context,
MessageDispatchInterceptorChain<CommandMessage> chain) {
logger.info("Dispatching command: {}", message.type().name());
return chain.proceed(message, context);
}
}
Spring Boot’s InterceptorAutoConfiguration automatically:
-
Discovers all beans implementing
MessageHandlerInterceptor<T> -
Discovers all beans implementing
MessageDispatchInterceptor<T> -
Registers them with the appropriate components based on their generic
Messagetype -
Applies them to the corresponding message buses
|
Be aware that interceptors are registered on all applicable components based on their generic type.
That means, defining a |
|
You can control interceptor application order using Spring’s
|
Component-specific interceptor registration
For advanced scenarios where interceptors should only apply to specific components, use the factory pattern:
configurer.registerCommandHandlerInterceptor(
HandlerInterceptorFactory.of(
(config, componentType, componentName) -> {
// Only intercept OrderAggregate commands
if (componentType.equals(OrderAggregate.class)) {
return new OrderValidationInterceptor();
}
return null; // No interceptor for other components
}
)
);
The factory receives:
-
Configuration config- Access to framework configuration -
Class<?> componentType- The component class (for example, entity, projection) -
String componentName- The component’s registered name (may be null)
Return null to skip interceptor registration for that component.