Event Handlers
An Event Handler is a method that is capable of handling an EventMessage.
As such, the method will react to the occurrences within an application.
In Axon, an object may declare several event handlers.
This object is referred to as an Event Handling Component.
In most cases, each of these methods is annotated with @EventHandler.
When drafting an @EventHandler annotated method, the declared parameters of the method define which events it will receive.
If users prefer not to use annotations, they’re able to manual subscribe every event handler as a lambda with the EventHandlingComponent, as shown here.
Arguably the most important parameter of an event handler is the first parameter which refers to the payload of an EventMessage.
If the event handler does not need access to the payload of the message, you can specify the expected payload type on the @EventHandler annotation.
Do not configure the payload type on the annotation if you want the payload to be passed as a parameter. For a complete list of all parameters, we refer to this section.
Registering event handlers
Event handlers can be provided in two forms to Axon:
-
As an
@EventHandlerannotated method. -
As an
EventHandlerimplementation or lambda.
This split resembles the difference between autodetected and declarative configuration. The former is, for example, used by Spring to automate the registration of your handlers. The latter would be preferred, for example, by those aiming to use no annotations at all.
Event handlers are registered as part of event handling components. In turn, these event handling components are registered as a part of configuring event processors.
|
Annotating event messages
As explained further here, it’s recommended to annotate all event message classes with
If the |
The exception to this flow is Spring, for which the registration of event handlers is separated from the configuration of event processors. The examples below show how to event handlers can be configured:
-
Autodetected - Spring Boot
-
Autodetected - Configuration API
-
Declarative - Configuration API
In a Spring Boot environment, simply adding the Event Handling Components to the Application Context is sufficient:
@Component
public class AnnotatedEventHandlingComponent {
@EventHandler
public void on(SomeEvent event) {
// ...
}
}
Axon’s auto-configuration will automatically detect and register all message handlers and assign them to the appropriate event processor. Be sure to read this section if you want to comprehend how you can influence the automated handler registration.
When using Axon’s configuration API, event handlers can only be registered as part of event processor configuration.
However, you are not inclined to register every event handler separately, assuming you’re using the @EventHandler annotation.
When assigning event handling components to an event processor, you will be able to use the autodetected method for this flow:
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
import org.axonframework.messaging.eventhandling.configuration.EventHandlingComponentsConfigurer;
import org.axonframework.messaging.eventhandling.processing.streaming.pooled.PooledStreamingEventProcessorsConfigurer;
public class AxonConfig {
public void configureEventProcessing(MessagingConfigurer configurer) {
configurer.eventProcessing(eventConfigurer -> eventConfigurer.pooledStreaming(
this::configurePooledStreamingProcessor
));
}
private PooledStreamingEventProcessorsConfigurer configurePooledStreamingProcessor(
PooledStreamingEventProcessorsConfigurer pooledStreamingConfigurer
) {
return pooledStreamingConfigurer.processor(
"my-processor",
config -> config.eventHandlingComponents(this::configureHandlingComponent).notCustomized()
);
}
private EventHandlingComponentsConfigurer.AdditionalComponentPhase configureHandlingComponent(
EventHandlingComponentsConfigurer.RequiredComponentPhase componentConfigurer
) {
return componentConfigurer.autodetected(c -> new AnnotatedEventHandlingComponent());
}
}
With autodetected(), the framework scans the component for @EventHandler annotated methods and registers them automatically.
This is the recommended approach when using annotations.
Be sure to read this section if you want to comprehend how you can influence the automated handler registration.
For complete details on event processor configuration and assigning handlers to processors, see Event Processors.
When using Axon’s configuration API, event handlers can only be registered as part of event processor configuration.
If your prefer not to use annotations, you will have to subscribe every event handler separately with an EventHandlingComponent
You’re automatically guided to that point when you are configuring an event processor and choose the declarative flow:
import org.axonframework.messaging.core.MessageStream;
import org.axonframework.messaging.core.QualifiedName;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
import org.axonframework.messaging.eventhandling.EventHandlingComponent;
import org.axonframework.messaging.eventhandling.SimpleEventHandlingComponent;
import org.axonframework.messaging.eventhandling.configuration.EventHandlingComponentsConfigurer;
import org.axonframework.messaging.eventhandling.processing.streaming.pooled.PooledStreamingEventProcessorsConfigurer;
public class AxonConfig {
public void configureEventProcessing(MessagingConfigurer configurer) {
configurer.eventProcessing(eventConfigurer -> eventConfigurer.pooledStreaming(
this::configurePooledStreamingProcessor
));
}
private PooledStreamingEventProcessorsConfigurer configurePooledStreamingProcessor(
PooledStreamingEventProcessorsConfigurer pooledStreamingConfigurer
) {
return pooledStreamingConfigurer.processor(
"my-processor",
config -> config.eventHandlingComponents(this::configureHandlingComponent).notCustomized()
);
}
private EventHandlingComponentsConfigurer.AdditionalComponentPhase configureHandlingComponent(
EventHandlingComponentsConfigurer.RequiredComponentPhase componentConfigurer
) {
return componentConfigurer.declarative(c -> {
EventHandlingComponent eventHandlingComponent = new SimpleEventHandlingComponent();
eventHandlingComponent.subscribe(
new QualifiedName("OrderPlaced"),
(event, context) -> {
OrderPlacedEvent eventPayload = event.payloadAs(OrderPlacedEvent.class);
// process events
return MessageStream.empty();
}
);
return eventHandlingComponent;
});
}
}
With declarative(), you explicitly create an EventHandlingComponent (typically SimpleEventHandlingComponent) and subscribe handler logic using the subscribe() methods.
This gives you full control without relying on annotation scanning.
Use this when you want to avoid annotation-based "magic" and prefer a more explicit configuration style.
For complete details on event processor configuration and assigning handlers to processors, see Event Processors.
Annotated event handler name resolution
@EventHandler annotated methods can specify which event they handle in two ways.
Most common is the use of the @Event annotation on your event class.
This allows you to explicitly define its name, namespace, and version:
@Event(
namespace = "com.example.orders", // Defaults to package name
name = "OrderPlaced", // Local name; defaults to simple class name
version = "1.0.0" // Defaults to "0.0.1"
)
public record OrderPlacedEvent(
String orderId,
String customerId
) {
}
The event handler’s name is composed of namespace + name (for example, "com.example.orders.OrderPlaced").
Now let’s assume we have an @EventHandler handling the OrderPlacedEvent:
@EventHandler
public void on(OrderPlacedEvent event) {
// ...
}
Axon’s autodetected configuration will find the OrderPlacedEvent event handler and note the @Event handler.
The resulting name is then used to register the handler.
The component in charge of this resolution is the MessageTypeResolver, which is explained in more detail here.
If you need to override the name used to register the event handler or if the event payload does not contain the @Event annotation at all, you should define the eventName attribute of the @EventHandler:
@EventHandler(eventName = "com.example.orders.OrderPlaced")
public void handleOrderPlaced(Map<String, Object> eventData) {
// Handles events with qualified name "com.example.orders.OrderPlaced"
// Payload is converted to Map at handling time
}
Handler resolution - How a published event reaches an event handler
When an event is published, Axon determines which event handler methods to invoke based on the event type.
In Axon, all matching event handlers are invoked for each event.
Matching is done based on the QualifiedName of the handler and the MessageType of the event (both the name and type are explained in more detail here).
When you have manually subscribed every event handler with an Event Handling Component, the matching based on the names is leading.
When you’ve taken the autodetected flow (as shown here), the framework uses the following rules to determine matching handlers:
-
Axon identifies all event handlers that can handle the event type by matching the type with the name of the handler.
-
For each matching handler, Axon evaluates whether it can resolve all required parameters.
-
All handlers for which parameters can be resolved are invoked.
-
If no suitable event handler is found, the event is ignored (or handled by the processor’s default behavior).
Consider the following Event Handling Component sample:
public class OrderEventHandler {
@EventHandler
public void on(OrderPlacedEvent event) {
// Update read model
}
@EventHandler
public void logOrderPlaced(OrderPlacedEvent event, @MetadataValue("userId") String userId) {
// Log the event - this handler is ALSO invoked for OrderPlacedEvent
}
@EventHandler
public void on(OrderShippedEvent event) {
// Handle order shipped
}
}
In this example, when an OrderPlacedEvent is published, both the on() and logOrderPlaced() methods will be invoked.
Event handler return values
Event handlers through their nature cannot have a concrete return object. This stems from the fact that an event handler can handle events from 5 minutes ago, 5 days ago, or 5 years ago. As such, an event handler is conceptually not able to "directly" respond to the component that published the event.
Hence, the typical return type of any event handler is void:
@EventHandler
public void on(OrderPlacedEvent event) {
// Update state, no return value
}
However, you are able to make asynchronous event handlers, by for example using a CompletableFuture<Void>:
@EventHandler
public CompletableFuture<Void> on(OrderPlacedEvent event) {
return asyncService.processOrder(event.getOrderId());
}
The framework will wait for the CompletableFuture to complete before considering the event handled.
The same option applies for Mono:
@EventHandler
public Mono<Void> on(OrderPlacedEvent event) {
return reactiveService.processOrder(event.getOrderId());
}
Lastly, you’re able to use Axon’s MessageStream to support either of the above flows.
When registering an EventHandler as a lambda directly, you are even expected to return a MessageStream:
import org.axonframework.messaging.core.MessageStream;
import org.axonframework.messaging.core.QualifiedName;
import org.axonframework.messaging.eventhandling.EventHandlingComponent;
import org.axonframework.messaging.eventhandling.SimpleEventHandlingComponent;
import org.axonframework.messaging.eventhandling.TerminalEventMessage;
import org.axonframework.messaging.eventhandling.configuration.EventHandlingComponentsConfigurer;
public class AxonConfig {
// omitted event processing configurer methods...
private EventHandlingComponentsConfigurer.AdditionalComponentPhase configureHandlingComponent(
EventHandlingComponentsConfigurer.RequiredComponentPhase componentConfigurer
) {
return componentConfigurer.declarative(c -> {
EventHandlingComponent eventHandlingComponent = new SimpleEventHandlingComponent();
eventHandlingComponent.subscribe(
new QualifiedName("OrderPlaced"),
(event, context) -> {
OrderPlacedEvent eventPayload = event.payloadAs(OrderPlacedEvent.class);
// process events
return MessageStream.empty();
}
);
eventHandlingComponent.subscribe(
new QualifiedName("OrderDeclined"),
(event, context) -> {
OrderDeclinedEvent eventPayload = event.payloadAs(OrderDeclinedEvent.class);
AsyncService asyncService = context.component(AsyncService.class);
return MessageStream.fromFuture(asyncService.processOrderDeclined(eventPayload)
.thenApply(r -> TerminalEventMessage.INSTANCE))
.cast()
.ignoreEntries();
}
);
return eventHandlingComponent;
});
}
}