Command Handlers

Command handlers are the components that process commands and perform the corresponding business logic. Command handlers can be either stateless or stateful, depending on whether they need to maintain state across command invocations.

This page explains how to define command handlers, handle commands, and work with the key concepts like ProcessingContext, EventAppender, and message types.

Annotating command messages

It’s recommended to annotate all command message classes with @Command. This allows you to:

  • Define an explicit message type independent of the Java class name.

  • Version your queries for evolution over time.

  • Organize queries by bounded context using namespaces.

  • Leverage payload conversion at handling time for different representations.

If the @Command annotation is omitted, Axon will default to using the fully qualified class name as the command name, which tightly couples your message identity to your Java package structure.

Stateless vs stateful command handlers

Command handlers in Axon Framework fall into two categories:

  • Stateless command handlers: Regular components (registered objects or beans) that don’t maintain state in the handler itself between command invocations. Each command is processed independently. State may be accessed through injected services or repositories, but the handler itself doesn’t manage state.

  • Stateful command handlers: Components that maintain state in the handler itself across command invocations. These are typically event-sourced entities where state is built up from events and stored in the handler instance.

With recent versions of Axon Framework, the emphasis has shifted from modeling-centric (read: aggregate focus) to command-centric. Entities are now understood as one implementation pattern for stateful command handling, not a core framework concept. The focus is on handling commands effectively, with various patterns available to achieve that goal.

Stateless command handlers

Stateless command handlers are regular components with command handling methods that don’t maintain state in the handler itself. An example of command handling methods are methods annotated with @CommandHandler They may access state through injected services or repositories, but the handler instance itself is stateless.

import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
import org.springframework.stereotype.Component;

@Component (1)
public class GiftCardBulkOperationsHandler {

    private final GiftCardInventoryService inventoryService; (2)

    public GiftCardBulkOperationsHandler( GiftCardInventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    @CommandHandler (3)
    public void handle(BulkIssueCardsCommand cmd,
                       CommandDispatcher commandDispatcher) {
        // Access state through service, but don't maintain it in the handler
        int availableStock = inventoryService.getAvailableStock(cmd.shopId());
        if (availableStock < cmd.quantity()) {
            throw new IllegalStateException("Insufficient stock");
        }

        // Reserve stock and dispatch commands
        inventoryService.reserveStock(cmd.shopId(), cmd.quantity()); (4)

        for (int i = 0; i < cmd.quantity(); i++) {
            String cardId = UUID.randomUUID().toString();
            commandDispatcher.send(new IssueCardCommand(cardId, cmd.amount(), cmd.shopId()));
        }
    }
}
1 Stateless command handlers are regular objects registered via the Configuration API or Spring beans as is the case in this example.
2 State is accessed through injected services, not maintained in the handler itself.
3 Each command invocation is independent—no instance state is maintained between calls.
4 Commands change the conceptual state of the system (reserving stock, issuing cards).

Commands should focus on changing the conceptual state of a system. If a command only performs validation or queries without changing state, consider whether it should be implemented as a query instead.

Stateless handlers are useful for:

  • Commands that coordinate operations across multiple stateful handlers.

  • Commands that perform operations using state from external services or repositories.

  • Commands that don’t require building up and maintaining state within the handler itself.

Stateful command handlers

Stateful command handlers maintain state in the handler itself across command invocations. State is typically built up from events and used to make decisions about whether commands can be executed.

Axon Framework supports two paradigms for defining stateful command handlers:

  • Entity-centric: Define an entity class and place @CommandHandler methods directly on it.

  • Command-centric: Define command handlers as separate methods that receive the entity as a parameter, which Axon automatically loads.

Both approaches achieve the same goal but reflect different design preferences. The entity-centric approach groups all command handling logic with the entity state, while the command-centric approach keeps command handling logic separate from state management.

Entity-centric command handlers

In the entity-centric model, you define a class marked with @EventSourcedEntity and place command handlers directly on it:

import org.axonframework.eventsourcing.annotation.EventSourcedEntity;
import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
import org.axonframework.messaging.commandhandling.annotation.Command;
import org.axonframework.messaging.eventhandling.gateway.EventAppender;

@Command(routingKey = "cardId") (1)
public record IssueCardCommand(String cardId, int amount, String shopId) {
}

@Command(routingKey = "cardId")
public record RedeemCardCommand(String cardId, String transactionId, int amount) {
}

@EventSourcedEntity (2)
public class GiftCard {

    private String id;
    private int remainingValue;

    @CommandHandler (3)
    public static String issue(IssueCardCommand cmd, EventAppender eventAppender) {
        if (cmd.amount() <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount()));
        return cmd.cardId();
    }

    @CommandHandler (4)
    public void handle(RedeemCardCommand cmd, EventAppender eventAppender) {
        if (cmd.amount() <= 0) {
            throw new IllegalArgumentException("amount <= 0");
        }
        if (cmd.amount() > remainingValue) { (5)
            throw new IllegalStateException("amount > remaining value");
        }
        eventAppender.append(new CardRedeemedEvent(id, cmd.transactionId(), cmd.amount()));
    }

    // Event sourcing handlers update state
    @EventSourcingHandler
    private void on(CardIssuedEvent event) {
        this.id = event.cardId();
        this.remainingValue = event.amount();
    }

    @EventSourcingHandler
    private void on(CardRedeemedEvent event) {
        this.remainingValue -= event.amount();
    }

    @EntityCreator (6)
    protected GiftCard() {
        // Required no-arg constructor for reconstitution
    }
}
1 The @Command annotation on the command class defines the message type and routing key. The routingKey attribute specifies which property contains the identifier for routing. Commands with the same routing key are routed to the same handler instance to avoid optimistic concurrency conflicts.
2 The @EventSourcedEntity annotation (non-Spring) or @EventSourced (Spring) marks the class as an event-sourced entity. If such an event-sourced entity contains command handlers, those will be automatically discovered.
3 A creational command handler (static method) creates a new instance by appending events.
4 An instance command handler (instance method) operates on an existing instance, requiring it to be loaded first based on the entity identifier. The task of finding the entity identifier is performed by the EntityIdResolver, which defaults to the AnnotatedEntityIdResolver. The AnnotatedEntityIdResolver in turn checks for the existence of an @TargetEntityId annotated field or getter on the command.
5 Instance handlers have access to current state for decision-making.
6 The entity creator of the event-sourced entity, as described in more detail here. A no-arg constructor in this example.

Command-centric command handlers

In the command-centric model, you define command handlers as separate methods that receive the entity as a parameter. Axon Framework automatically loads the entity instance based on the command’s routing key:

import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
import org.axonframework.extension.spring.stereotype.EventSourced;
import org.springframework.stereotype.Component;

// The entity definition with state and event sourcing handlers
@EventSourcedEntity
public class GiftCard {

    private String id;
    private int remainingValue;

    @EventSourcingHandler
    private void on(CardIssuedEvent event) {
        this.id = event.cardId();
        this.remainingValue = event.amount();
    }

    @EventSourcingHandler
    private void on(CardRedeemedEvent event) {
        this.remainingValue -= event.amount();
    }

    public String getId() {
        return id;
    }

    public int getRemainingValue() {
        return remainingValue;
    }

    @EntityCreator
    protected GiftCard() {
        // Required no-arg constructor for reconstitution
    }
}

// Separate command handlers that receive the entity as a parameter
@Component (1)
public class GiftCardCommandHandlers {

    @CommandHandler (2)
    public String handle(IssueCardCommand cmd, EventAppender eventAppender) {
        // Creational handler - no entity parameter needed
        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount()));
        return cmd.cardId();
    }

    @CommandHandler
    public void handle(RedeemCardCommand cmd,
                       @InjectEntity GiftCard card, (3)
                       EventAppender eventAppender) {
        // Instance handler - entity automatically loaded by Axon
        if (cmd.amount() <= 0) {
            throw new IllegalArgumentException("amount <= 0");
        }
        if (cmd.amount() > card.getRemainingValue()) { (4)
            throw new IllegalStateException("amount > remaining value");
        }
        eventAppender.append(new CardRedeemedEvent(card.getId(), cmd.transactionId(), cmd.amount()));
    }
}
1 Command handlers are defined in a separate component (not on the entity itself).
2 Creational handlers don’t receive an entity parameter—they create the entity through publication of the first event.
3 Instance handler receives entities when the parameter is annotated with @InjectEntity. Axon automatically loads it based on the command’s routing key. Other options are available and specified in the @InjectEntity documentation.
4 The handler accesses the entity to make business decision. This can either be done by invoking entity-specific decision methods or by inspecting the state (as shown in the sample above).

The command-centric approach aligns closest with [Vertical Slice-based Architecture](https://www.baeldung.com/java-vertical-slice-architecture). This style is useful when:

  • You want to keep command handling logic separate from entity state management.

  • You have multiple entities that share similar command handling patterns.

  • You prefer to organize code by command/use case rather than by entity.

  • You want to easily test command handling logic without the entity class.

Creational and instance handlers

Both entity-centric and command-centric stateful handlers support two handler types:

  • Creational handlers: Create new instances (static factory methods on the entity, or separate methods without entity parameter)

  • Instance handlers: Operate on existing instances (instance methods on the entity, or separate methods with entity parameter)

The examples below show the entity-centric approach. For command-centric examples, see the previous section.

Creational handlers

Creational handlers are static methods with @CommandHandler that create new entity instances by appending events:

@EventSourced
public class GiftCard {

    @CommandHandler
    public static String issue(IssueCardCommand cmd, EventAppender eventAppender) { (1)
        if (cmd.amount() <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }

        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount())); (2)

        return cmd.cardId(); (3)
    }

    @EntityCreator (4)
    protected GiftCard() {
        // Required no-arg constructor for reconstitution
    }
}
1 Static method marked with @CommandHandler creates the entity.
2 Events are appended to create and initialize the entity. The entity instance itself doesn’t need to be created or returned by the handler.
3 The method can return whatever value makes sense for the caller—typically the entity identifier, but could be any result type.
4 The entity creator of the event-sourced entity, as described in more detail here. A no-arg constructor in this example.

Instance handlers

Instance handlers operate on existing instances that are loaded based on their identifier:

@EventSourced
public class GiftCard {

    private String id;
    private int remainingValue;

    @CommandHandler (1)
    public void handle(RedeemCardCommand cmd, EventAppender eventAppender) {
        if (cmd.amount() > remainingValue) {
            throw new IllegalStateException("Insufficient balance");
        }
        eventAppender.append(new CardRedeemedEvent(id, cmd.transactionId(), cmd.amount()));
    }

    @EntityCreator (2)
    protected GiftCard() {
    }
}
1 Instance methods with @CommandHandler require an existing instance to be loaded based on the entity identifier. The task of finding the entity identifier is performed by the EntityIdResolver, which defaults to the AnnotatedEntityIdResolver. The AnnotatedEntityIdResolver in turn checks for the existence of an @TargetEntityId annotated field or getter on the command.
2 The entity creator of the event-sourced entity, as described in more detail here. A no-arg constructor in this example.

The framework automatically loads the correct instance based on the command’s routing key before invoking the handler.

Entity creation with @EntityCreator

In event sourcing, events are used to reconstruct the state of an entity. To be able to reconstruct the state, an initial entity instance should be constructed. The @EntityCreator annotation specifies how to create that initial instance. The @EntityCreator is mandatory when writing annotation-based entities, as they are in charge of telling Axon Framework how you prefer to initialize an entity.

There are three patterns for entity creation:

Pattern 1: Empty instance creation

When the @EntityCreator annotation is placed on a no-arg constructor, Axon Framework constructs an empty instance of that entity:

@EventSourced
public class GiftCard {

    private String id;
    private boolean created = false;
    private int remainingValue;

    @EntityCreator (1)
    public GiftCard() {
    }

    @CommandHandler (2)
    public static void handle(IssueCardCommand cmd, EventAppender eventAppender) {
        if (created) {
            throw new IllegalStateException("Card already issued");
        }
        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount()));
    }

    @EventSourcingHandler
    private void on(CardIssuedEvent event) {
        this.id = event.id();
        this.created = true;
        this.remainingValue = event.amount();
    }
}
1 A no-argument constructor annotated with @EntityCreator. This constructor will be invoked to create the initial entity for further instance command handling.
2 A static command handler must be used for the creation command, since no instance exists to handle it yet. Once the event is published, future loads will use the @EntityCreator to create the instance from that event.

Pattern 2: Identifier-only creation

Besides pattern one, where a no-arg constructors was annotated with the @EntityCreator annotation, you can also place the @EntityCreator on a constructor that includes an entity identifier parameter. The entity identifier is required to be annotated with @InjectEntityId, as shown in the sample below:

@EventSourced
public class GiftCard {

    private String id;
    private boolean created;
    private int remainingValue;

    @EntityCreator (1)
    public GiftCard(@InjectEntityId String id) { (2)
        this.id = id;
        this.created = false;
    }

    @CommandHandler (3)
    public static void handle(IssueCardCommand cmd, EventAppender eventAppender) {
        if (created) {
            throw new IllegalStateException("Card already issued");
        }
        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount()));
    }

    @EventSourcingHandler
    private void on(CardIssuedEvent event) {
        this.created = true;
        this.remainingValue = event.amount();
    }
}
1 @EntityCreator on a constructor that takes only the identifier to creates the initial instance. This constructor will be invoked to create the initial entity for further instance command handling.
2 The entity identifier parameter, annotated with @InjectEntityId. The identifier is resolved from the first event payload. The annotation is required by Axon Framework to be able to differentiate between an entity identifier parameter and event parameter (as used in pattern 3).
3 A static command handler must be used for the creation command, since no instance exists to handle it yet. Once the event is published, future loads will use the @EntityCreator to create the instance from that event.

Use @InjectEntityId to disambiguate the entity identifier from the event payload. Without this annotation, the first parameter is assumed to be the event payload.

Pattern 3: Creation from origin event

Besides no-argument and identifier-only @EntityCreator annotated constructors, you can use the first event to construct the initial entity:

@EventSourced
public class GiftCard {

    private final String id;
    private final int initialAmount;

    @EntityCreator (1)
    public GiftCard(CardIssuedEvent event, @InjectEntityId String id) {
        this.id = event.cardId(); // or use the injected id parameter
        this.initialAmount = event.amount();
    }

    @CommandHandler (2)
    public static String issue(IssueCardCommand cmd, EventAppender eventAppender) {
        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount()));
        return cmd.cardId();
    }
}
1 @EntityCreator on a constructor that takes the first event that can then be used to initialize the entity state. Can be combined with an @InjectEntityId annotated entity identifier parameter if desired. This constructor will be invoked to create the initial entity for further instance command handling.
2 A static command handler must be used for the creation command, since no instance exists to handle it yet. Once the event is published, future loads will use the @EntityCreator to create the instance from that event.

Understanding the relationship:

  • @EntityCreator defines how to create entity instances (either through a no-arg, identifier-only, or first-event-based constructor).

  • @CommandHandler on static methods handles creation commands and appends events.

  • @CommandHandler on instance methods handles commands on loaded entities.

When a command arrives Axon Framework checks if the command is targeted towards a creational or instance command handler:

  1. For an instance command handler, Axon loads the entity using @EntityCreator first. Then the appropriate instance command handler is invoked on that instance.

  2. For a creational command handler, the static command handling method is invoked first. Then, the appropriate event sourcing handlers are triggered to set the new state.

Both annotations work together in the entity lifecycle, but serve distinct purposes.

Polymorphic entities

Axon Framework allows for polymorphism in your entities when desired. Please check out the following example that shows how to construct a polymorphic entity:

@EventSourcedEntity(concreteTypes = {StandardCard.class, PremiumCard.class}) (1)
public abstract class GiftCard {

    @EntityCreator (2)
    public static GiftCard create(@InjectEntityId CardId id) {
        if (id.type() == CardType.STANDARD) {
            return new StandardCard(id);
        } else {
            return new PremiumCard(id);
        }
    }
}
1 The base or parent class is on purpose made abstract, to signal it is not the concrete entity type. Furthermore, the @EventSourcedEntity (or @EventSourced for Spring environments) annotation specifies the concrete types of the entity through the concreteTypes field.
2 Static factory method with @EntityCreator can return different subtypes based on the identifier.

Command message types

In Axon Framework, commands are identified by a MessageType, which consists of a QualifiedName and version. This decouples the logical identity of a command from its (Java) representation.

Declaring command types

Use the @Command annotation on your command class to declare its message type:

@Command(
    namespace = "com.example.giftcard",  (1)
    name = "IssueCard",                   (2)
    version = "1.0.0",                    (3)
    routingKey = "cardId"                 (4)
)
public record IssueCardCommand(String cardId, int amount, String shopId) {
}
1 The namespace typically represents the bounded context. Defaults to the package name of the class.
2 The name is the business/domain name of the command. Defaults to the simple class name.
3 The version allows evolution of the command structure over time. Defaults to "0.0.1".
4 The routingKey specifies which property to use for routing the command to the correct handler instance.

Payload conversion at handling time

A powerful feature of Axon Framework is that the payload can be converted to the handler’s expected type at handling time. This means different handlers in different JVMs can receive the same command in different Java representations:

// Original command class
@Command(name = "IssueCard", version = "1.0.0")
public record IssueCardCommand(String cardId, int amount, String shopId) {
}

// Alternative representation for a different handler
public record IssueCardDto(String cardId, int amount) {
}

// Handler using original type
@CommandHandler
public void handle(IssueCardCommand cmd, EventAppender eventAppender) {
    // Receives IssueCardCommand
}

// Handler in a different component AND different JVM using alternative representation
@CommandHandler(commandName = "com.example.IssueCard")
public void handle(IssueCardDto dto) {
    // Same command, but converted to IssueCardDto
    // Axon converts the payload automatically
}

The framework uses Converters to transform the payload to match the handler’s parameter type. This eliminates many use cases for upcasters—simple type conversions happen during handling rather than requiring explicit upcasting logic.

Upcasters are still needed for structural changes to events in the event store. However, for commands and for handlers that simply need data in a different format, payload conversion is more appropriate.

Handling commands with ProcessingContext

The ProcessingContext is a fundamental concept in Axon Framework, backing the internal concept of the UnitOfWork. When a command is handled, Axon automatically creates a ProcessingContext and makes it available to all components involved in processing the command.

You can inject the ProcessingContext as a parameter in any command handler:

@CommandHandler
public void handle(
    RedeemCardCommand cmd,
    EventAppender eventAppender,
    ProcessingContext context (1)
) {
    // The ProcessingContext is available for interaction
    // For example, you can store and retrieve resources
    MyCustomService service = context.getResource(MY_SERVICE_KEY);

    // Perform business logic
    if (cmd.amount() > remainingValue) {
        throw new IllegalStateException("Insufficient balance");
    }

    // Append events
    eventAppender.append(new CardRedeemedEvent(id, cmd.transactionId(), cmd.amount()));
}
1 The ProcessingContext is automatically injected by the framework. It provides access to correlation data, resources, and lifecycle management.

For more details on ProcessingContext, see Processing Context.

Publishing events with EventAppender

To publish events as a result of command handling, the EventAppender should be used. The EventAppender is aware of the current ProcessingContext, ensuring that Axon Framework can populate correlation data. The EventAppender must be injected as a parameter in your command handlers:

@CommandHandler
public GiftCard(IssueCardCommand cmd, EventAppender eventAppender) { (1)
    // Validate command
    if (cmd.amount() <= 0) {
        throw new IllegalArgumentException("Amount must be positive");
    }

    // Append event(s)
    eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount())); (2)
}

@CommandHandler
public void handle(RechargeCardCommand cmd, EventAppender eventAppender) {
    // Can append multiple events
    eventAppender.append(
        new CardRechargedEvent(id, cmd.amount()),
        new CardRechargeRecordedEvent(id, cmd.transactionId())
    ); (3)
}
1 The EventAppender is injected as a method parameter alongside the command.
2 Use eventAppender.append() to publish one or more events. Events are applied to the entity and published when the ProcessingContext commits.
3 Multiple events can be appended in a single call.

The EventAppender is ProcessingContext-aware and ensures events are published within the correct processing context. Never try to store or reuse an EventAppender across different command invocations.

Business logic and state changes

For stateful command handlers, there is a critical separation of concerns:

  • Command handlers validate whether the handler is in the correct state to execute the command. If yes, they publish events. If not, they throw an exception, ignore the command, or publish entirely different events,depending on business requirements.

  • Event sourcing handlers apply state changes based on events. They must never contain business logic—only state updates.

State changes should never occur in command handling functions. Event sourcing handlers should be the only methods where state is updated. Failing to follow this principle means the handler will miss state changes when it is being sourced from its events.

@EventSourcedEntity
public class GiftCard {

    private String id;
    private int remainingValue;

    @CommandHandler // Command handler: validation and decision-making
    public void handle(RedeemCardCommand cmd, EventAppender eventAppender) {
        // Business logic validation
        if (cmd.amount() <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        if (cmd.amount() > remainingValue) { (1)
            throw new IllegalStateException("Insufficient balance");
        }

        // Decision: publish event
        eventAppender.append(new CardRedeemedEvent(id, cmd.transactionId(), cmd.amount()));
        (2)
    }

    @EventSourcingHandler // Event sourcing handler: state changes only
    private void on(CardRedeemedEvent event) {
        this.remainingValue -= event.amount(); (3)
    }

    @EntityCreator
    protected GiftCard() {
    }
}
1 Command handler reads current state to make decisions.
2 Command handler does not modify state directly.
3 Event sourcing handler is the only place where state is modified.

The only state a stateful handler requires is the state it needs to make decisions. Handling an event is thus only required if the state change is necessary for future validation of other commands.

The Test Fixture will guard against unintentional state changes in command handling functions. It is strongly advised to provide thorough test cases for any stateful handler implementation.

Injectable parameters

Command handlers support several types of injectable parameters beyond just the command payload. Some of these are listed below:

@CommandHandler
public void handle(
    RedeemCardCommand command,              (1)
    CommandMessage commandMessage,          (2)
    Metadata metadata,                      (3)
    @MetadataValue("userId") String userId, (4)
    ProcessingContext processingContext,    (5)
    EventAppender eventAppender,            (6)
    CommandDispatcher commandDispatcher     (7)

) {
    // Handler logic
}
1 The command payload (first parameter, or explicitly typed).
2 The CommandMessage containing the command payload.
3 The entire Metadata block from the CommandMessage.
4 @MetadataValue extracts specific values from the Metadata in the CommandMessage.
5 ProcessingContext for accessing correlation data and resources.
6 EventAppender for publishing events.
7 CommandDispatcher for dispatching other commands from within this handler.

For a complete list of supported parameters, see Supported Parameters for Annotated Handlers.

Injecting multiple entities with @InjectEntity

Command handlers can work with multiple entity instances by using the @InjectEntity annotation. This allows you to inject different entities as parameters, which Axon will automatically load based on the command:

import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
import org.axonframework.modelling.annotation.InjectEntity;
import org.springframework.stereotype.Component;

@Command(routingKey = "sourceCardId") (1)
public record TransferBalanceCommand(
    String sourceCardId,
    String destinationCardId,
    String transactionId,
    int amount
) {
}

@Component
public class GiftCardTransferHandler {

    @CommandHandler
    public void handle(
        TransferBalanceCommand cmd,
        @InjectEntity GiftCard sourceCard, (2)
        @InjectEntity(idProperty = "destinationCardId") GiftCard destinationCard, (3)
        EventAppender eventAppender
    ) {
        // Validate source card
        if (sourceCard.getRemainingValue() < cmd.amount()) {
            throw new IllegalStateException("Insufficient balance on source card");
        }

        // Perform transfer
        eventAppender.append(
            new BalanceDeductedEvent(cmd.sourceCardId(), cmd.amount(), cmd.transactionId()),
            new BalanceAddedEvent(cmd.destinationCardId(), cmd.amount(), cmd.transactionId())
        );
    }
}
1 The command’s routingKey determines which card is the "primary" entity.
2 Without idProperty, the @InjectEntity behavior defaults to using the command’s routing key to load an entity.
3 With idProperty = "destinationCardId", Axon loads the destination card from a different property.

By specifying the idProperty attributes on @InjectEntity annotated parameters you can load an entity based on different field or getter present in the command. This is an example of how a command handler can load different entities.

Alternative: Manual loading with repository

If you have a different way of loading required state (for example, from a custom data source or with special loading logic), you can manually load entities using the repository. However, this is effectively the same as a stateless command handler—you’re just using services to access state:

@Component
public class GiftCardCustomLoadingHandler {

    private final Repository<String, GiftCard> repository; (1)
    private final CustomCardLookupService cardLookup;

    @CommandHandler
    public CompletableFuture<Void> handle(
        TransferBalanceByAccountCommand cmd,
        ProcessingContext context, (2)
        EventAppender eventAppender (3)
    ) {
        // Custom logic to determine which cards to use
        String sourceCardId = cardLookup.findActiveCardForAccount(cmd.sourceAccountId());
        String destCardId = cardLookup.findActiveCardForAccount(cmd.destinationAccountId());

        // Load both entities, extract them, and combine
        return repository.load(sourceCardId, context) (4)
                .thenApply(ManagedEntity::entity) (5)
                .thenCombine(
                    repository.load(destCardId, context)
                            .thenApply(ManagedEntity::entity), (6)
                    (sourceCard, destCard) -> {
                        // Perform operations on loaded entities
                        sourceCard.handle(
                            new RedeemCardCommand(sourceCardId, cmd.transactionId(), cmd.amount()),
                            eventAppender
                        );
                        destCard.handle(
                            new RechargeCardCommand(destCardId, cmd.amount()),
                            eventAppender
                        );

                        return null; (7)
                    }
                );
    }
}
1 Repository interface with proper generic types (ID type and Entity type).
2 ProcessingContext is injected to pass to repository operations.
3 EventAppender is injected as a parameter to publish events.
4 repository.load() returns a CompletableFuture<ManagedEntity<ID, E>>.
5 thenApply(ManagedEntity::entity) extracts the entity from the ManagedEntity wrapper.
6 Both load operations extract their entities, so thenCombine() receives the actual GiftCard instances directly.
7 Return null since the command handler return type is CompletableFuture<Void>.

Explicit command handler subscription

By default, Axon subscribes command handlers based on the @CommandHandler annotation, assuming that the fully qualified class name of the first parameter is the command you want to handle. However, you can also explicitly configure which commands a handler should handle:

@CommandHandler(commandName = "com.example.IssueCard") (1)
public void handleIssueCard(MyCustomCommandType cmd) {
    // Handle command
}

@CommandHandler(
    commandName = "com.example.RedeemCard",
    payloadType = RedeemCardDto.class (2)
)
public void handleRedeem(RedeemCardDto cmd) {
    // Handle command
}
1 The commandName attribute specifies the exact command name (MessageType) to handle.
2 The payloadType attribute specifies the Java type to convert the payload to.

This is useful when:

  • You want to handle commands by their logical name rather than Java class.

  • You need payload conversion to a specific type.

  • You’re integrating with external systems that use different message formats.

Configuration examples

Spring Boot configuration

When using Spring Boot, command handlers are automatically discovered and registered by using the @CommandHandler annotation on methods inside Spring beans:

@EventSourced (1)
public class GiftCard {
    @CommandHandler
    public GiftCard(IssueCardCommand cmd, EventAppender eventAppender) {
        eventAppender.append(new CardIssuedEvent(cmd.cardId(), cmd.amount()));
    }

    protected GiftCard() {
    }
}

@Component (2)
public class GiftCardValidationHandler {
    @CommandHandler
    public void handle(ValidateCardCommand cmd) {
        // Stateless handler logic
    }
}
1 Stateful handlers are discovered via @EventSourced annotation.
2 Stateless handlers are discovered when they are regular Spring beans, as in this example through use of the @Component annotation.

Configuration API

Without Spring, you can register command handlers using the Configuration API:

    void buildConfiguration() {
        // Create the Event Sourcing configurer
        EventSourcingConfigurer configurer = EventSourcingConfigurer.create();

        // Register stateful event-sourced entity
        configurer.registerEntity(
            EventSourcedEntityModule.autodetected(String.class, GiftCard.class)
        );

        // Register stateless command handler
        configurer.registerCommandHandlingModule(
            CommandHandlingModule.named("gift-card-validation")
                                 .commandHandlers()
                                 .annotatedCommandHandlingComponent(config ->
                                     new GiftCardValidationHandler(config.getComponent(QueryDispatcher.class))
                                 )
        );

        // Build and start the configuration
        AxonConfiguration configuration = configurer.build();
        configuration.start();
    }

For more details on configuration, see Configuration.