Command Handlers

Command handlers process commands and perform the corresponding business logic. A command handler decides whether a requested change is valid and, if so, applies it by appending events, invoking services, or dispatching further commands.

Command handlers are stateless when they do not need prior state to process the command (this page). When prior state is required, the handler either lives on the entity itself (see Event-sourced entities) or receives the entity as a parameter via @InjectEntity (see Stateful command handlers).

Stateless command handlers

Command handlers are regular components with methods annotated with @CommandHandler. They may access state through injected services or repositories, but the handler instance itself holds no state between invocations.

import org.axonframework.examples.demo.university.shared.application.notifier.NotificationService;
import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
import org.springframework.stereotype.Component;

@Component (1)
public class FacultyAnnouncementCommandHandler {

    private final NotificationService notificationService; (2)

    public FacultyAnnouncementCommandHandler(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    @CommandHandler (3)
    public void handle(SendFacultyAnnouncement command) {
        // Access state through service, but don't maintain it in the handler
        notificationService.sendNotification(
                new NotificationService.Notification(command.recipientId(), command.message())); (4)
    }
}
1 Command handlers are regular objects registered via the Configuration API or as Spring beans, as 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 (here, delivering an announcement through the notification service).

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.

When command handling requires entity state, see Event-sourced entities for handlers placed directly on the entity class, or Stateful command handlers for the @InjectEntity pattern.

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.

Annotate command classes with @Command to define an explicit message type. This lets you set a stable name and namespace independent of the Java class name, version the command for evolution over time, and enable payload conversion at handling time.

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

Declaring command types

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

@Command(
    namespace = "faculty",            (1)
    name = "IssueStudentTranscript",  (2)
    version = "1.0.0",                (3)
    routingKey = "studentId"          (4)
)
public record IssueStudentTranscript(StudentId studentId, CourseId courseId) {
}
1 The namespace typically represents the bounded context. When no namespace is specified on @Command, Axon searches for the presence of the @Namespace annotation (as described her). When no @Namespace annotation is found, then namespace falls back 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(namespace = "faculty", name = "IssueStudentTranscript", version = "1.0.0")
public record IssueStudentTranscript(StudentId studentId, CourseId courseId) {
}

// Alternative representation for a different handler
public record IssueStudentTranscriptDto(String studentId, String courseId) {
}

// Handler using the original type
@CommandHandler
public void handle(IssueStudentTranscript command) {
    // Receives IssueStudentTranscript with typed StudentId / CourseId
}

// Handler in a different component AND different JVM using the alternative representation
@CommandHandler(commandName = "faculty.IssueStudentTranscript")
public void handle(IssueStudentTranscriptDto dto) {
    // Same command, but converted to the DTO representation
    // 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(
    SendFacultyAnnouncement command,
    ProcessingContext context (1)
) {
    notificationService.sendNotification(
            new NotificationService.Notification(command.recipientId(), command.message()));

    context.runOnCommit(ctx -> notificationService.sendNotification( (2)
            new NotificationService.Notification("audit", "Announcement delivered to " + command.recipientId())));
}
1 The ProcessingContext is automatically injected by the framework. It exposes the active unit of work, registered application components, and lifecycle hooks.
2 runOnCommit registers a callback that fires once the unit of work commits successfully. Use it for side effects that should only happen after the command processing completes: metrics, audit trails, or dispatching follow-up notifications outside the transactional boundary.

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 static CourseId handle(CreateCourse command, EventAppender eventAppender) { (1)
    if (command.capacity() <= 0) {
        throw new IllegalArgumentException("Capacity must be positive");
    }
    eventAppender.append(new CourseCreated(Ids.FACULTY_ID, command.courseId(), command.name(), command.capacity())); (2)
    return command.courseId();
}

@CommandHandler
public void handle(RenameCourse command, EventAppender eventAppender) {
    eventAppender.append( (3)
            new CourseRenamed(Ids.FACULTY_ID, command.courseId(), command.name())
    );
}
1 EventAppender is injected as a handler parameter alongside the command. Creational handlers are declared static, no entity instance exists yet at this point.
2 Use eventAppender.append() to publish one or more events. Events are published when the ProcessingContext commits.
3 append accepts a single event, a varargs sequence, or a List<?> when several events are produced in one 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.

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(
    RenameCourse 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.

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 = "faculty.IssueStudentTranscript") (1)
public void handleIssueTranscript(IssueStudentTranscriptDto dto) {
    // Handle command
}

@CommandHandler(
    commandName = "faculty.RenameCourse",
    payloadType = RenameCourseDto.class (2)
)
public void handleRename(RenameCourseDto dto) {
    // 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

Three approaches are available to register a stateless command handler:

  1. Declarative: register a handler function programmatically with CommandHandlingModule.

  2. Autodetected: annotate methods with @CommandHandler and register the containing class with CommandHandlingModule.autodetectedCommandHandlingComponent().

  3. Spring Boot: annotate methods with @CommandHandler inside a Spring bean. Axon’s auto-configuration detects and registers them automatically.

The non-Spring examples below assume a single EventSourcingConfigurer exists for the application, constructed once in main() and passed to a configuration method.

The examples below illustrate a stateless SendFacultyAnnouncement command handler that delegates to a NotificationService registered as an application component:

public record SendFacultyAnnouncement(String recipientId, String message) {
}

Declarative

Register a command handler function without annotations.

import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
import org.axonframework.examples.demo.university.shared.application.notifier.NotificationService;
import org.axonframework.messaging.commandhandling.SimpleCommandHandlingComponent;
import org.axonframework.messaging.commandhandling.configuration.CommandHandlingModule;
import org.axonframework.messaging.core.MessageStream;
import org.axonframework.messaging.core.MessageTypeResolver;

public class FacultyAnnouncementConfiguration {

    public static EventSourcingConfigurer configure(EventSourcingConfigurer configurer) {
        return configurer.registerCommandHandlingModule(
            CommandHandlingModule.named("FacultyAnnouncement")
                                 .commandHandlers()
                                 .commandHandlingComponent(config -> { (1)
                                     MessageTypeResolver resolver = config.getComponent(MessageTypeResolver.class);
                                     return SimpleCommandHandlingComponent
                                             .create("FacultyAnnouncementComponent")
                                             .subscribe(
                                                     resolver.resolveOrThrow(SendFacultyAnnouncement.class).qualifiedName(), (2)
                                                     (command, context) -> {
                                                         var payload = command.payloadAs(SendFacultyAnnouncement.class);
                                                         context.component(NotificationService.class) (3)
                                                                .sendNotification(new NotificationService.Notification(
                                                                        payload.recipientId(), payload.message()));
                                                         return MessageStream.empty().cast(); (4)
                                                     });
                                 })
        );
    }
}
1 Build the command handling component inside a ComponentBuilder lambda so the Configuration is available for component lookups during registration.
2 Resolve the command’s qualified name through the MessageTypeResolver rather than constructing it directly. This honors any custom name or namespace declared on a @Command annotation and falls back to the fully qualified class name when none is present.
3 Retrieve any registered component from the ProcessingContext. The NotificationService was registered separately as part of the application’s configuration.
4 Stateless command handlers return a MessageStream. Use MessageStream.empty().cast() when no result is produced.

Autodetected

Annotate methods with @CommandHandler and register the containing class. Axon scans the class for @CommandHandler methods automatically.

import org.axonframework.examples.demo.university.shared.application.notifier.NotificationService;
import org.axonframework.messaging.commandhandling.annotation.CommandHandler;

public class FacultyAnnouncementCommandHandler {

    private final NotificationService notificationService;

    public FacultyAnnouncementCommandHandler(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    @CommandHandler
    public void handle(SendFacultyAnnouncement command) {
        notificationService.sendNotification(
                new NotificationService.Notification(command.recipientId(), command.message()));
    }
}

Register the handler class:

import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
import org.axonframework.examples.demo.university.shared.application.notifier.NotificationService;
import org.axonframework.messaging.commandhandling.configuration.CommandHandlingModule;

public class FacultyAnnouncementConfiguration {

    public static EventSourcingConfigurer configure(EventSourcingConfigurer configurer) {
        return configurer.registerCommandHandlingModule(
            CommandHandlingModule.named("FacultyAnnouncement")
                                 .commandHandlers()
                                 .autodetectedCommandHandlingComponent(config ->
                                         new FacultyAnnouncementCommandHandler(
                                                 config.getComponent(NotificationService.class)))
        );
    }
}

Spring Boot

Annotate methods with @CommandHandler inside any Spring bean. Axon’s auto-configuration discovers and registers them automatically.

import org.axonframework.examples.demo.university.shared.application.notifier.NotificationService;
import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
import org.springframework.stereotype.Component;

@Component
public class FacultyAnnouncementCommandHandler {

    private final NotificationService notificationService;

    public FacultyAnnouncementCommandHandler(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    @CommandHandler
    public void handle(SendFacultyAnnouncement command) {
        notificationService.sendNotification(
                new NotificationService.Notification(command.recipientId(), command.message()));
    }
}

For registering event-sourced entities, see Event-sourced entities. For more details on configuration, see Configuration.