Supported Parameters for Annotated Handlers

This chapter provides an exhaustive list of all the possible parameters for annotated message handling functions. The framework resolves the parameters for any message handling function through an internal mechanism, called the ParameterResolver. The ParameterResolver, built by a ParameterResolverFactory, is in charge of inserting the parameters for annotated command, event, and query handlers.

The set of ParameterResolvers can be extended if custom (or not yet) supported parameters should be injected in to your annotated handlers. You can configure additional ParameterResolvers by implementing the ParameterResolverFactory interface and configuring the new implementation. For more specifics on configuring custom `ParameterResolver`s we suggest reading this section.

Supported parameters for command handlers

By default, @CommandHandler annotated methods allow the following parameter types.

Parameter designation Purpose

The first parameter

The first parameter is always the payload of the command message. It may also be of type Message or CommandMessage, which contain the payload. Using the Message/CommandMessage requires the @CommandHandler annotation to define the command’s name the handler processes (by setting the commandName property on the annotation).

type: Metadata

Will contain the entire metadata of a CommandMessage.

annotated with @MetadataValue

Will resolve the metadata value with the key as indicated on the annotation. If required is false (default), null is passed when the metadata value is not present. If required is true, the resolver will not match and prevent the method from being invoked when the metadata value is not present.

type: Message or CommandMessage

Will get the complete message, with both the payload and the metadata. Resolving the entire Message/CommandMessage is helpful if a method needs several metadata fields or other properties of the message. It is best to declare a parameter of the payload type as the first parameter, followed by a parameter of type CommandMessage if you need both.

type: ProcessingContext

Will get the current ProcessingContext injected. The ProcessingContext allows command handlers to register actions to be performed at specific stages of processing or gain access to the resources registered with it.

type: String annotated with @MessageIdentifier

Will resolve the identifier of the handled CommandMessage.

type: EventAppender

Will inject the EventAppender for publishing events from within a command handler. This is useful when you need to publish events within an entity’s context. Ensures correlation data is taken care of as the EventAppender is processing context aware.

type: CommandDispatcher

Will inject the CommandDispatcher for dispatching commands from within a command handler. This is useful for command orchestration or dispatching follow-up commands. Ensures correlation data is taken care of as the CommandDispatcher is processing context aware.

Spring Bean

If the application runs in a Spring environment, any Spring Bean can be resolved. The parameter can be annotated with @Qualifier if a specific version of the bean should be wired.

Supported parameters for event handlers

By default, @EventHandler annotated methods allow the following parameter types.

Parameter designation Purpose

The first parameter

The first parameter is always the payload of the event message. It may also be of type Message or EventMessage, which contain the payload. Using the Message/EventMessage requires the @EventHandler annotation to define the event’s name the handler processes (by setting the eventName property on the annotation).

type: Metadata

Will contain the entire metadata of an EventMessage.

annotated with @MetadataValue

Will resolve the metadata value with the key as indicated on the annotation. If required is false (default), null is passed when the metadata value is not present. If required is true, the resolver will not match and prevent the method from being invoked when the metadata value is not present.

type: Message or EventMessage

Will get the complete message, with both the payload and the metadata. Resolving the entire Message/EventMessage is helpful if a method needs several metadata fields or other properties of the message. It is best to declare a parameter of the payload type as the first parameter, followed by a parameter of type EventMessage if you need both.

type: ProcessingContext

Will get the current ProcessingContext injected. The ProcessingContext allows event handlers to register actions to be performed at specific stages of processing or gain access to the resources registered with it.

type: String annotated with @MessageIdentifier

Will resolve the identifier of the handled EventMessage.

type: java.time.Instant annotated with @Timestamp

Will resolve to the timestamp of the EventMessage. The resolved timestamp is the time at which the event was generated.

type: TrackingToken

Will have the current token related to the processed event injected. Note that this will only work for streaming event processors, as otherwise there is no token attached to the events.

type: ReplayStatus

Will resolve to the ReplayStatus indicating whether the event is delivered as a regular event or a replay event during event replay operations.

annotated with @ReplayContext

Will resolve to replay context values that were registered when the replay was started. This is only available during event replay and can be used to access replay-specific configuration or state.

type: CommandDispatcher

Will inject the CommandDispatcher for dispatching commands from within an event handler. This is useful when events should trigger command execution. Ensures correlation data is taken care of as the CommandDispatcher is processing context aware.

type: EventAppender

Will inject the EventAppender for publishing events from within an event handler. This is useful when an event handler needs to publish new events in response to the event being handled. Ensures correlation data is taken care of as the EventAppender is processing context aware.

type: QueryUpdateEmitter

Will inject the QueryUpdateEmitter for emitting updates to subscription queries. This is useful when query handlers need to push incremental updates or notify subscribers of changes. Ensures correlation data is taken care of as the QueryUpdateEmitter is processing context aware.

Spring Bean

If the application runs in a Spring environment, any Spring Bean can be resolved. The parameter can be annotated with @Qualifier if a specific version of the bean should be wired.

Aggregate-specific parameters

If an aggregate-based event storage solution is used, there are a couple of additional parameter resolvers you can use:

  • A String annotated with @AggregateType - Will resolve the aggregate type name when the event originated from an aggregate. This provides the type of aggregate that published the event.

  • A Long or long annotated with @SequenceNumber - Will resolve to the sequence number of events originating from an aggregate. This parameter provides the order in which the event was generated within the aggregate that published it. Only available for events published by aggregates.

  • A String annotated with @SourceId - Will resolve to the aggregate identifier when the event originated from an aggregate. This parameter provides the identifier of the aggregate that published the event. Only available for events published by aggregates.

None of the above are applicable when the event store is based of the dynamic consistency boundary principle, since events are not aggregate-specific in that scenario.

Supported parameters for query handlers

By default, @QueryHandler annotated methods allow the following parameter types.

Parameter designation Purpose

The first parameter

The first parameter is always the payload of the query message. It may also be of type Message or QueryMessage, which contain the payload. Using the Message/QueryMessage requires the @QueryHandler annotation to define the query’s name the handler processes (by setting the queryName property on the annotation).

type: Metadata

Will contain the entire metadata of a QueryMessage.

annotated with @MetadataValue

Will resolve the metadata value with the key as indicated on the annotation. If required is false (default), null is passed when the metadata value is not present. If required is true, the resolver will not match and prevent the method from being invoked when the metadata value is not present.

type: Message or QueryMessage

Will get the complete message, with both the payload and the metadata. Resolving the entire Message/QueryMessage is helpful if a method needs several metadata fields or other properties of the message. It is best to declare a parameter of the payload type as the first parameter, followed by a parameter of type QueryMessage if you need both.

type: ProcessingContext

Will get the current ProcessingContext injected. The ProcessingContext allows query handlers to register actions to be performed at specific stages of processing or gain access to the resources registered with it.

type: String annotated with @MessageIdentifier

Will resolve the identifier of the handled QueryMessage.

type: CommandDispatcher

Will inject the CommandDispatcher for dispatching commands from within a query handler. While generally discouraged (queries should not have side effects), this may be useful in specific scenarios. Ensures correlation data is taken care of as the CommandDispatcher is processing context aware.

type: EventAppender

Will inject the EventAppender for publishing events from within a query handler. While generally discouraged (queries should not have side effects), this may be useful in specific scenarios. Ensures correlation data is taken care of as the EventAppender is processing context aware.

Spring Bean

If the application runs in a Spring environment, any Spring Bean can be resolved. The parameter can be annotated with @Qualifier if a specific version of the bean should be wired.

Examples

Here are some examples demonstrating the use of different parameter types:

Basic command handler with ProcessingContext

@CommandHandler
public void handle(PlaceOrderCommand command,
                   EventAppender appender) {
    // Make business decision and append event...
    PaymentRequestedEvent event = new PaymentRequestedEvent(
            command.orderId(),
            command.amount()
    );
    appender.append(event);
}

Event handler with ProcessingContext

@EventHandler
public void on(OrderPlacedEvent event,
               ProcessingContext context) {
    // Register cleanup action
    context.whenComplete(pc -> cleanupResources());
    processEvent(event);
}

Event handler with metadata and timestamp

@EventHandler
public void on(OrderPlacedEvent event,
               @MetadataValue("userId") String userId,
               @Timestamp Instant timestamp) {
    // userId is extracted from metadata
    // timestamp contains when the event was created
    updateProjection(event, userId, timestamp);
}

Query handler with subscription query support

@QueryHandler
public OrderSummary handle(OrderQuery query,
                           QueryUpdateEmitter updateEmitter) {
    OrderSummary summary = loadSummary(query.orderId());

    OrderUpdate update = createUpdate(summary);

    // Register for updates to send incremental changes
    updateEmitter.emit(OrderQuery.class,
                       q -> q.orderId().equals(query.orderId()),
                       update);

    return summary;
}

Event handler dispatching follow-up commands

@EventHandler
public CompletableFuture<Void> handle(OrderPlacedEvent event,
                                      CommandDispatcher dispatcher) {
    // Update the state based on the event
    updateState(event);

    // Dispatch follow-up command
    CommandResult result = dispatcher.send(new ShipOrderCommand(event.orderId()));

    // Return the CompletableFuture so the handler only completes when the command finishes
    return result.getResultMessage()
                 .thenAccept(r -> logger.info("Shipping initiated"))
                 .exceptionally(ex -> {
                     logger.error("Failed to initiate shipping: {}", ex.getMessage());
                     // Handle error appropriately
                     return null;
                 });
}

Event handler accessing replay context

@EventHandler
public void on(OrderPlacedEvent event,
               ReplayStatus replayStatus,
               @ReplayContext String replayReason) {
    if (replayStatus == ReplayStatus.REPLAY) {
        // Special handling during replay
        logger.info("Replaying event due to: {}", replayReason);
        // Skip side effects during replay
    } else {
        // Normal processing
        sendEmailNotification(event);
    }
}