Hands-on tutorial: Axon Framework CQRS Tutorial — Step by Step (2026)
GitHub · LinkedIn · About · YouTube
Last updated by Kindson Munonye — June 29, 2026
📚 Tutorial hub: Microservices & CQRS tutorials · CRUD with Spring Boot backend
Prerequisites: Java, Spring Boot basics, REST APIs. Familiarity with Spring Boot CRUD helps.
Estimated reading time: ~20 minutes · Last updated: June 29, 2026
Video companion: CQRS & Event Sourcing explained (YouTube)
📚 Browse all tutorials: Microservices & CQRS tutorials hub · CRUD with Spring Boot backend
In this simple article, I would tell you all the components that make up a CQRS and Event Sourcing application. So if you know how all this files hook together, then you can build any application using the CQRS pattern.
Scenario: Order Placement Application
A user places an order(OrderCreatedCommand) via a UI application built in Angular(step by step here). An order is created with the quantity provided by the user and the item selected by the user. Once this order is created, a StockUpdated event is published which updates the product stock by depreciating it by the amount in the order. User is able to see list of orders and list of products on the UI as well.
Complete applications on Github
Order processing app with Axon
All the Component
Now we would partition the component into three groups:
We also have a 4th group, the Events components, which are spread through all the other components. Also note that there are some overlap between these components. Let’s now discuss these components;
1. API/Command Components
These are components involved in the creation and handling of commands. I have used some codes to represent each component so that later, I would explain how they interact. The Aggregates are what is stored in the write store(remember in CQRS, there is both read store and write store). Commands are operations that need to be performed. In this case there are three commands for: adding a product, creating an order and updating the product stock. You can add more. There are also three events, command handler and event-sourcing handlers. Event-sourcing is simply a way to save and retrieve data from the write store.

2. Query Components
These components are responsible for creation and response to queries. I have outlined three queries, but it can be more. The Read Model and Read Repositories is exactly the same as in normal ORM(Object Relational Mapping). The ProductView and OrderView are entities while the ProductRepository and OrderRepository are Jpa or Crud repository interface. Finally, for all the queries, there must be a query handler. Four of them are provides.

3. Common Components
The common components are involved in routing messages between various parts of the architecture. There are three gateways: the CommandGateway sends new commands into the command bus; the QueryGateway queries new queries into the query bus; the EventGateway publishes new events to the event bus. Also the buses are like channels to carry messages across:
- Command Bus: a channel for commands moving from the gateway to the command handler
- Event Bus: a channel to move events from the gateway to the event handler
- Query Bus: a channel to move queries from the gateway to the query handler

I’ll recommend you follow my step by step to build the application so you can see how all these artefacts interact. Finally, I would like to differentiate between and EventHandler and an EventSourcingHandlers.
Both EventHandler and EventSourcingHandler responds to events. However, EventSourcingHandlers are concerned with reading and writing aggregates to the write store. Therefore, changes made to the state of an aggregate are persisted in the write store by the EventSourcingHandler. EventHandler on the other hands, can be made to perform a wide range of actions depending on the application requirements.
Additional CQRS and Event Sourcing Resources
- Complete CQRS Application with Axon Framework(Video)
- CQRS Step by Step with Axon Framework
- Microservices with Spring Boot
- Simple Explanation of CQRS and Event Sourcing
Axon Framework Overview
Axon Framework is the most popular Java library for CQRS and Event Sourcing. It provides:
- Command handlers — process write operations (CreateOrder, UpdateInventory)
- Event handlers — react to domain events and update read models
- Aggregate roots — enforce business rules and emit events
- Event store — persist events (Axon Server or embedded)
Axon separates the command side (writes) from the query side (reads), which is the core of CQRS.
Setting Up Axon Server
Download and run Axon Server (local dev):
# Download from https://axoniq.io/product-overview/axon-server
# Run standalone
./axonserver
# Default ports: 8024 (HTTP), 8124 (gRPC)
Add Axon dependencies to your Spring Boot pom.xml:
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-starter</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-server-connector</artifactId>
<version>4.9.3</version>
</dependency>Configure application.properties:
axon.axonserver.servers=localhost:8124
spring.application.name=order-serviceCommand Side — Aggregate and Command Handler
Define a command and an aggregate that applies events:
// CreateOrderCommand.java
public class CreateOrderCommand {{
@TargetAggregateIdentifier
private final String orderId;
private final String productId;
public CreateOrderCommand(String orderId, String productId) {{
this.orderId = orderId; this.productId = productId;
}}
}}
// OrderAggregate.java
@Aggregate
public class OrderAggregate {{
@AggregateIdentifier private String orderId;
protected OrderAggregate() {{}}
@CommandHandler
public OrderAggregate(CreateOrderCommand cmd) {{
AggregateLifecycle.apply(new OrderCreatedEvent(cmd.getOrderId(), cmd.getProductId()));
}}
@EventSourcingHandler
public void on(OrderCreatedEvent event) {{
this.orderId = event.getOrderId();
}}
}}Event Handler — Updating the Read Model
Event handlers project events into a query-optimized database (the “Q” in CQRS):
@Component
public class OrderProjection {{
@Autowired private OrderRepository orderRepository;
@EventHandler
public void on(OrderCreatedEvent event) {{
orderRepository.save(new OrderView(event.getOrderId(), event.getProductId(), "CREATED"));
}}
}}The read model can use a different schema, database, or even Elasticsearch — optimized for queries without affecting the write model.
Query Side — REST Controller
@RestController
@RequestMapping("/orders")
public class OrderQueryController {{
@Autowired private OrderRepository orderRepository;
@GetMapping("/{id}")
public OrderView getOrder(@PathVariable String id) {{
return orderRepository.findById(id).orElseThrow();
}}
}}Commands go through Axon (CommandGateway); queries hit the read model directly — this is CQRS in practice.
CQRS vs Traditional CRUD
Your Angular CRUD tutorial uses a single REST model for reads and writes. CQRS splits these when:
- Read and write patterns differ significantly (e.g. complex reports vs simple commands)
- You need a full audit trail via event sourcing
- Multiple services consume the same event stream
Next Steps
- Run Axon Server locally and connect your Spring Boot app
- Implement one aggregate with two events (Create + Update)
- Add a projection that builds a query table
- Explore the Microservices tutorials hub for related architecture guides
Additional CQRS and Event Sourcing Resources
- Complete CQRS Application with Axon Framework (Video playlist)
- Axon Framework official documentation
- Microservices & CQRS tutorials hub
Event Sourcing in Depth
In event sourcing, the event store is the source of truth — not the current row in a database table. To rebuild state, replay events:
OrderCreatedEvent→ order status = CREATEDOrderShippedEvent→ order status = SHIPPEDOrderDeliveredEvent→ order status = DELIVERED
Axon replays events into aggregates automatically via @EventSourcingHandler. Snapshots can speed up replay for aggregates with thousands of events.
Sagas and Process Managers
When one command triggers workflows across multiple aggregates (place order → reserve inventory → charge payment), use Axon Sagas:
@Saga
public class OrderPlacementSaga {{
@StartSaga
@SagaEventHandler(associationProperty = "orderId")
public void on(OrderCreatedEvent event) {{
// send ReserveInventoryCommand
}}
@SagaEventHandler(associationProperty = "orderId")
public void on(InventoryReservedEvent event) {{
// send ProcessPaymentCommand
}}
}}Sagas coordinate long-running business processes without tight coupling between services.
Spring Boot + Axon Integration Tips
- Enable Axon with
@EnableAxonon your Spring Boot application class - Inject
CommandGatewayin REST controllers to dispatch commands - Use
@ProcessingGroupon event handlers for parallel processing - Configure separate data sources for event store vs read model if needed
@RestController
public class OrderCommandController {{
@Autowired CommandGateway commandGateway;
@PostMapping("/orders")
public CompletableFuture<String> create(@RequestBody CreateOrderRequest req) {{
String id = UUID.randomUUID().toString();
return commandGateway.send(new CreateOrderCommand(id, req.getProductId()));
}}
}}CQRS in Microservices Architecture
In a microservices system, each bounded context can have its own command model and one or more read models:
- Order Service — commands: CreateOrder, CancelOrder; events: OrderCreated, OrderCancelled
- Inventory Service — listens to OrderCreated, emits InventoryReserved
- Reporting Service — builds denormalized views from event stream
Compare this to the monolithic CRUD approach in our Angular + Spring Boot CRUD series — CQRS adds complexity but scales reads and writes independently.
Testing CQRS Components
// Unit test aggregate with Axon Test fixture
@Test
void shouldCreateOrder() {{
fixture.givenNoPriorActivity()
.when(new CreateOrderCommand("id-1", "product-1"))
.expectEvents(new OrderCreatedEvent("id-1", "product-1"));
}}Axon provides AggregateTestFixture and SagaTestFixture for fast, in-memory testing without Axon Server.
Common CQRS Mistakes to Avoid
- Using CQRS for simple CRUD apps (unnecessary complexity)
- Sharing the same database schema for commands and queries (defeats the purpose)
- Not handling eventual consistency on the read side (UI must tolerate delay)
- Skipping event versioning strategy (events live forever)
