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.
Annotation-based interceptors
Axon Framework 5 supports declaring interceptor methods directly on handling components using dedicated annotations:
-
@CommandHandlerInterceptor: intercepts command handlers on the same component -
@QueryHandlerInterceptor: intercepts query handlers on the same component -
@EventHandlerInterceptor: intercepts event handlers on the same component
Two styles are available:
-
Before-interceptor: a
voidmethod without aMessageHandlerInterceptorChainparameter. The framework runs the method before the handler and automatically proceeds the chain when it returns normally. -
Surround-interceptor: a method that returns
MessageStream<?>and declares aMessageHandlerInterceptorChainparameter. The method controls whether and when to callchain.proceed(message, context).
-
Axon Framework 4
-
Axon Framework 5
public class OrderCommandHandler {
@CommandHandlerInterceptor
public void intercept(CommandMessage<?> command, InterceptorChain interceptorChain) throws Exception {
if (!isAuthorized(command)) {
throw new AccessDeniedException("Not authorized");
}
interceptorChain.proceed();
}
@CommandHandler
public void handle(PlaceOrderCommand command) {
// Handle the command
}
}
import org.axonframework.messaging.commandhandling.annotation.CommandHandlerInterceptor;
import org.axonframework.messaging.core.MessageHandlerInterceptorChain;
import org.axonframework.messaging.core.MessageStream;
public class OrderCommandHandler {
// Surround-interceptor: full control over the chain
@CommandHandlerInterceptor
MessageStream<?> authorize(
CommandMessage command,
MessageHandlerInterceptorChain<CommandMessage> chain,
ProcessingContext context
) {
if (!isAuthorized(command)) {
return MessageStream.failed(new AccessDeniedException("Not authorized"));
}
return chain.proceed(command, context);
}
// Before-interceptor: runs before the handler, chain proceeds automatically
@CommandHandlerInterceptor
void log(CommandMessage command) {
logger.info("Handling command: {}", command.qualifiedName());
}
@CommandHandler
void handle(PlaceOrderCommand command, ProcessingContext context) {
// Handle the command
}
}
For full documentation and examples for all three message types, see Component Message Intercepting.
Migration of @ExceptionHandler
The @ExceptionHandler annotation works in Axon Framework 5 with the same intent as in Axon Framework 4: handling exceptions thrown from message handler methods in the same class.
The payloadType attribute has been removed. Narrowing is now done solely by exception type via resultType, and optionally by message type (command, event, or query) via messageType.
-
Axon Framework 4
-
Axon Framework 5
public class OrderCommandHandler {
@ExceptionHandler(resultType = IllegalStateException.class)
public void handleIllegalState(IllegalStateException exception, CommandMessage<?> message) {
// Handle exception for all commands
}
}
public class OrderCommandHandler {
@ExceptionHandler(resultType = IllegalStateException.class)
public void handleIllegalState(IllegalStateException exception) {
// Handle exception for all commands on this component
}
// Narrow to a specific exception type
@ExceptionHandler(resultType = ValidationException.class)
public void handleValidation(ValidationException exception) {
// Only handles ValidationException (and its subtypes)
}
}
For full documentation on @ExceptionHandler, see Component Message Intercepting - @ExceptionHandler.