Command Processing

Command processing is usually tuned to keep writes for the same entity in order while preserving concurrency for unrelated commands. The default behavior in Axon Framework 5 already handles the common case, so most applications only need to customize sequencing when the default routing key is not the right boundary.

Command sequencing

Command sequencing ensures that commands targeting the same entity are handled sequentially. This prevents optimistic locking conflicts when several commands arrive for the same entity at the same time.

When sequencing matters

Use sequencing when:

  • several commands can target the same entity concurrently

  • the entity changes are not safe to apply in parallel

  • you want the framework to serialize conflicts before they reach the repository

How it works

Command sequencing is implemented by the CommandSequencingInterceptor. It is a handler interceptor, so it runs after routing and before the command handler executes.

  1. The interceptor asks the configured SequencingPolicy for a sequence identifier.

  2. Commands with the same identifier are processed sequentially relative to each other.

  3. Commands with different identifiers can still execute concurrently.

  4. Commands for which the policy returns Optional.empty() bypass sequencing entirely.

The policy is applied at command handling time, so it does not change dispatching or routing behavior. That makes it a good fit for fine-grained control over concurrency without changing handlers.

The SequencingPolicy interface

The SequencingPolicy interface determines which commands should be sequenced together:

public interface SequencingPolicy {

    Optional<Object> sequenceIdentifierFor(CommandMessage command, ProcessingContext context);
}

Axon Framework provides a few useful policies:

  • RoutingKeySequencingPolicy (default) sequences commands by routing key. This is the default policy and matches the common entity identifier use case.

  • NoOpSequencingPolicy disables sequencing entirely and always returns an empty sequence identifier.

You can also implement a custom policy when the routing key is not the right boundary. Common alternatives are metadata-driven sequencing, tenant-based sequencing, or payload-property sequencing.

Configuration

Custom sequencing policy

The following policy sequences commands by tenant identifier in metadata:

public class TenantBasedSequencingPolicy implements SequencingPolicy {

    @Override
    public Optional<Object> sequenceIdentifierFor(CommandMessage command, ProcessingContext context) {
        return Optional.ofNullable(command.metadata().get("tenantId"));
    }
}

Register the policy as the command sequencing policy:

  • Axon Configuration API

  • Spring Boot auto configuration

public void configureSequencingPolicy(MessagingConfigurer configurer) {
    configurer.componentRegistry(cr -> cr.registerComponent(
            SequencingPolicy.class,
            c -> new TenantBasedSequencingPolicy())
    );
}
@Configuration
class MessagingConfiguration {

    @Bean
    public SequencingPolicy<? super CommandMessage> commandSequencingPolicy() {
        return new TenantBasedSequencingPolicy();
    }
}

If a command does not need sequencing, return Optional.empty(). That lets the framework fall back to concurrent execution for those commands.

Disabling sequencing

If command handling is already serialized by another mechanism, register NoOpSequencingPolicy.INSTANCE.

public void configureSequencingPolicy(MessagingConfigurer configurer) {
    configurer.componentRegistry(cr -> cr.registerComponent(
            SequencingPolicy.class,
            c -> NoOpSequencingPolicy.INSTANCE)
    );
}