This file provides guidance to AI Agents (Claude Code, Codex, Gemini, Cursor etc.) when working with code in this repository.
Axon Framework is a framework for building evolutionary, event-driven microservice systems based on Domain-Driven Design (DDD), Command-Query Responsibility Separation (CQRS), and Event Sourcing principles. This is Axon Framework 5, a major version under development with significant architectural changes from version 4.
- Simplicity First: Make every change as simple as possible. Impact minimal code.
- No Laziness: Find root causes. No temporary fixes. Senior developer standards.
- Minimal Impact: Changes should only touch what's necessary. Avoid introducing bugs.
- Spec-first: Enter plan mode for non-trivial tasks (3+ steps or architectural decisions).
- Subagent strategy: Use subagents liberally to keep main context clean. Offload research and parallel analysis. One task per subagent.
- Self-improvement: After corrections, update the relevant CLAUDE.md or a file under
.claude/rules/, or introduce a new one. Write rules that prevent the same mistake. - Verification: Run tests, check build, suggest user verification. Ask: "Would a staff engineer approve this?"
- Elegance: For non-trivial changes, pause and ask "is there a more elegant way?" Skip for simple fixes.
- Autonomous bug fixing: When given a bug report, just fix it. Point at logs/errors, then resolve. Zero hand-holding.
- JDK 21 Base: Framework requires Java 21. During implementation use Java 21 features like sealed classes.
- Reactive Support: Framework supports both reactive and imperative programming styles without enforcing either
- Modular Design: Breaking apart monolithic modules into focused, composable components
- No ThreadLocals: Internally avoids ThreadLocals, only used at edges for imperative style
- Composition over Inheritance: Favors composition patterns throughout the codebase
- Declarative Configuration: Moving from annotation-heavy to declarative configuration approaches
| Branch Pattern | Purpose |
|---|---|
main |
Next minor or major release. Always contains all commits — patch branch changes flow upward into main. |
axon-[2-5].[0-12].x |
Patch release branches (e.g., axon-5.0.x). Changes to a patch branch are merged upward through higher patch branches and ultimately into main. |
bug/[issue-number]/[name] |
Bug fixes. Prefix groups all bug branches together; issue number sub-groups related branches. |
enhancement/[issue-number]/[name] |
Enhancements to existing features. |
feature/[issue-number]/[name] |
New features. |
documentation/[issue-number]/[name] |
Documentation-only changes. |
dependency-upgrade/[issue-number]/[name] |
Dependency version upgrades. |
- use a subject line
- separate the subject line from the description by an empty line
- focus on the how and why in the description, not the what
- use the actual issue number at the start of the subject line, for example
[#2343] - reference any related issue numbers in the commit message
- avoid including unrelated changes in the same commit
Maven wrapper is used (./mvnw). Key commands:
# Full build with tests
./mvnw clean verify
# Build skipping tests
./mvnw clean install -DskipTests
# Build with code coverage (JaCoCo)
./mvnw -Dcoverage clean verify
# Build including example modules
./mvnw -Pexamples clean verify
# Build with integration tests
./mvnw -Pintegration-test clean verify
# Run all unit tests
./mvnw clean test
# Run a single test class
./mvnw test -pl messaging -Dtest=SimpleCommandBusTest
# Run a single test method
./mvnw test -pl messaging -Dtest=SimpleCommandBusTest#methodName
# Run integration tests for a specific module
./mvnw -Pintegration-test verify -pl integrationtests -Dtest=ClassName- When writing tests avoid Mock whenever possible, try to use simplest implementation. Or use some recording implementations if needed. Ask me if it's available or you need to create your own, if you cannot find. Always ask me before doing into Mocking or custom implementation if you don't find existing one.
- Mockito Spy is justified when behavior-based assertions alone cannot validate the interaction — e.g., verifying a decorator prevents calls to the delegate (
verify(delegate, never())), or verifying the delegate receives correct arguments (verify(delegate).method(eq(expected))). Wrap a real implementation withspy()and useverify/clearInvocationsas needed. Outside of these cases, prefer state-based assertions. - While testing do not focus on implementation details like implemented interfaces, always test the behaviour by API usage.
- Test Utilities: Located in
messaging/src/test/java/org/axonframework/utils/ - Additional Rules:
- Always use JUnit5, AssertJ (prefer AssertJ assertions over normal JUnit5), Awaitility
- Use
// given / /when // then(with spaces between // and the section name) convention with sections separated by such comments - Always create sample events using EventTestUtils
- Always use JUnit5 @Nested classes to group logically connected tests cases (like for same method, same given section etc.)
- Do not add @DisplayName for test methods, try to make method names self-explanatory and add meaningful comments in given-when-then sections if needed
Test naming conventions:
- Unit tests (Surefire):
*Test.java,*Tests.java,*Test_*.java,*Tests_*.java - Integration tests (Failsafe):
*IntegrationTest.java,*IntegrationTests.java,IT*.java,*IT.java,*ITCase.java
common
├── conversion
│ └── update
└───────┐
messaging ← CommandBus, EventBus, QueryBus, interceptors, annotations. Core messaging infrastructure (commands, events, queries)
│
modelling ← Aggregates, Repositories, EntityMetamodel, StateManagerDomain modeling support (entities)
│
eventsourcing ← EventStore, EventStorageEngine, event sourcing repositories. Event sourcing implementation
│
test ← BDD test fixtures (Given-When-Then DSL)
Additional modules:
axon-server-connector/— Distributed communication with Axon Serverextensions/spring/— Spring Boot auto-configuration and starterextensions/metrics/— Dropwizard and Micrometer metricsextensions/tracing/— OpenTelemetry distributed tracingstash/legacy*/— Backward compatibility layers for older Axon versionsstash/migration/— OpenRewrite migration recipesstash/todo/— Axon Framework 4 parts to be ported to Axon Framework 5integrationtests/— Cross-module integration test suitebuild/parent/— Parent POM with dependency management\examples/— Example applications for smoke testing and demonstration (build with-Pexamples)docs/— Reference guide and how-to guides written in AsciiDoc, built with Antora
Example applications demonstrating framework features with different setups and stacks:
university-demo— Plain Java exampleuniversity-java-springboot3— Spring Boot 3 exampleuniversity-java-springboot4— Spring Boot 4 exampleframework4-springboot4— Framework 4 compatibility example
When a new feature is implemented, it should be demonstrated in an appropriate example application when possible. This helps validate the feature in a realistic setting and serves as living documentation.
Build examples: ./mvnw -Pexamples clean verify
Reference guide and how-to guides at docs/reference-guide/, written in AsciiDoc and built with Antora. Additional standalone guides cover topics like dead letter queues, deadlines, identifier generation, meta-annotations, and message handler customization.
After implementing a feature, the relevant documentation in docs/ should be updated to reflect the changes. See docs/CLAUDE.md for detailed documentation migration guidelines (Axon 4 to 5 terminology, style rules, verification workflow).
Everything flows through three message types, each with its own bus:
| Message Type | Bus | Registry | Handler Interface | Component Interface | Handler Annotation |
|---|---|---|---|---|---|
CommandMessage |
CommandBus |
CommandHandlerRegistry |
CommandHandler |
CommandHandlingComponent |
@CommandHandler |
EventMessage |
EventBus |
EventHandlerRegistry |
EventHandler |
EventHandlingComponent |
@EventHandler |
QueryMessage |
QueryBus |
QueryHandlerRegistry |
QueryHandler |
QueryHandlingComponent |
@QueryHandler |
All messages carry a payload + metadata. Message routing uses QualifiedName (derived from payload type or annotation attribute).
Handlers can be registered two ways:
- Annotation-based — classes with
@CommandHandler/@EventHandler/@QueryHandlermethods, wrapped inAnnotated*HandlingComponentfor discovery and registration. - Programmatic — implement
CommandHandler/EventHandler/QueryHandler(single handler) orCommandHandlingComponent/EventHandlingComponent/QueryHandlingComponent(component with multiple handlers andsupportedCommandNames()/supportedEventNames()/supportedQueryNames()) and subscribe directly to the registry.
- Messages: All communication through
CommandMessage,EventMessage,QueryMessage - Message Buses:
CommandBus,EventBus,QueryBusfor message routing - Message Handlers: Components that process messages (aggregates, sagas, projections)
- Message Interceptors: Cross-cutting concerns applied to message processing
- StreamableEventSource: Event streaming abstraction
- TrackingToken: Position tracking in event streams (
GlobalSequenceTrackingToken) - MessageStream: Reactive stream abstraction for message consumption
- Context: Resource management during message processing
- Command Side: Aggregates process commands, emit events
- Query Side: Event handlers build read models from events
- Event Store: Persistent event storage with streaming capabilities
- Snapshots: Aggregate state snapshots for performance
Major changes include:
- Decoupling message names from payload types
- Separating framework concerns from Spring/JPA dependencies
- Enhanced reactive programming support
- Simplified configuration model
- Process Manager pattern replacing Sagas
Handler annotations are meta-annotations on @MessageHandler:
@MessageHandler (base)
├── @CommandHandler (messageType = CommandMessage.class)
├── @EventHandler (messageType = EventMessage.class)
│ └── @EventSourcingHandler (for aggregate state evolution)
└── @QueryHandler (messageType = QueryMessage.class)
Discovery flow:
AnnotatedHandlerInspectorscans class methods for@MessageHandler(or meta-annotated)HandlerDefinitionSPI createsMessageHandlingMemberinstances (ServiceLoader-based, extensible)HandlerEnhancerDefinitionwraps members to add behavior (tracing, timeouts, etc.)Annotated*HandlingComponentwraps handler classes and registers with the bus
Handler method parameters are resolved via ParameterResolver / ParameterResolverFactory:
- Payload (first parameter by default)
ProcessingContextinjection@MetadataValuefor metadata fieldsEventAppenderfor appending events from command handlers- Command dispatcher for sending commands
- Custom resolvers via ServiceLoader SPI
Parameter resolution is async-first (CompletableFuture<T>).
Two interception levels:
MessageDispatchInterceptor— before dispatch, no ProcessingContext yet. Can reject or transform messages.MessageHandlerInterceptor— around handler invocation, with active ProcessingContext. Also available as@MessageHandlerInterceptorannotation on methods.
ProcessingContext extends ProcessingLifecycle and manages per-message processing:
- Type-safe resource storage via
ResourceKey<T> - Lifecycle phases (start, commit, rollback)
- Transaction management integration
- Supports branching for sub-contexts
MessageStream<M> is the async-first return type for message handling:
- Non-blocking with callback-based notification
- Supports
map,filter,peek,mapMessageoperations - Static constructors:
fromIterable(),fromItems()
EventStorageEngine— low-level persistence (JPA/JDBC/in-memory)EventStore— high-level event operationsEventSourcingRepository— reconstructs entities by replaying events- Events tagged via
TagResolverfor multi-stream filtering AppendCondition/SourcingConditionfor consistency control
EntityMetamodel— type-safe entity metadataStateManager— entity lifecycle and state transitionsRepository<E>— load/save by ID- Supports polymorphic entity hierarchies
BDD-style testing:
AxonTestFixturewith phases:AxonTestGiven→AxonTestWhen→AxonTestThen*- Recording buses:
RecordingCommandBus,RecordingEventBus,RecordingEventStore - Hamcrest-based matchers in
test/.../matchers/
- Java 21 required
- 4-space indentation, 120-character line limit, LF line endings, UTF-8
- IntelliJ code style: import from
axon_code_style.xmlat repo root - Import threshold:
class_count_to_use_import_on_demand = 99(effectively: no wildcard imports)
- Discover API essence - Design for future flexibility over current completeness
- Ease of use - Support bare-bones, declarative, and annotation-based styles
- Threading agnostic - No assumptions about threading model
- Dual paradigm support - Both reactive and imperative styles
- Annotation flexibility - Support both annotation-based and annotation-less design
- Use JSpecify
@NullMarkedannotation (org.jspecify.annotations.NullMarked) at the package level to express nullability contracts - By default, all parameters and return values are non-null under
@NullMarked - Use JSpecify
@Nullableannotation (org.jspecify.annotations.Nullable) to explicitly mark a parameter or return value as nullable - Jakarta
@Nonnulland@Nullableannotations are explicitly forbidden viabuild/checkstyle.xml - In implementations, enforce non-null parameters with
Objects.requireNonNullchecks
Core Writing Conventions:
- Original Style: Always use original recommended javadoc style.
- Fragment Style for Tags:
@param,@return, and@throwstags use fragment style — start with a lowercase letter, no trailing period. This applies even if surrounding existing code uses sentence style — new code must always use fragment style. Class-level and method-level description paragraphs remain normal sentences (capitalized, with periods).// CORRECT — fragment style (lowercase, no period) @param type the extension class @param <T> the extension type @return the extension instance, never {@code null} @throws AxonConfigurationException if the extension cannot be created // WRONG — sentence style (capitalized, with period) @param type The extension class. @param <T> The extension type. @return The extension instance, never {@code null}. @throws AxonConfigurationException If the extension cannot be created.
- Comprehensive Coverage: Document ALL public methods, constructors, and classes
- Internal Methods: Even
@Internalconstructors should be documented to explain why they're internal
Linking and References:
- Always Use
@linkTags: When mentioning any class or interface, use@linktags- Example:
{@link org.axonframework.eventhandling.pooled.PooledStreamingEventProcessor} - Use full qualified names for external packages
- Can use short names for classes in same package:
{@link EventProcessorConfiguration}
- Example:
- Method References: Use
@linkfor method references- Example:
{@link #defaults(BiFunction)} - External method:
{@link org.axonframework.configuration.MessagingConfigurer#eventProcessing(java.util.function.Consumer)}
- Example:
Documentation Structure:
- Class-Level Documentation Should Include:
- Purpose and main responsibility
- Architectural role (composite, delegate, etc.)
- Main benefits/use cases
- Framework integration details (who creates it, when, how to access)
- Complete usage example with proper API calls
@authorand@sincetags
- Method Documentation Should Include:
- Clear purpose statement
- Parameter descriptions with types linked
- Return value description
- Usage notes or common patterns
- Cross-references to related methods
Internal API Annotation:
- Mark Internal Code with
@Internal: Use@Internal(org.axonframework.common.annotation.Internal) on classes and methods that are not part of the public API. This applies to code extracted purely for internal organization ( e.g., helper classes, implementation details split out for readability). Internal code is still usable but may introduce breaking changes in minor/patch releases. Always document why the element is internal in its Javadoc.
Framework-Specific Guidelines:
- Emphasize Integration: Explain how components fit into the framework
- Who creates the component
- How users should access it (don't instantiate directly)
- Proper usage patterns through framework APIs
- Focus on Purpose Over Implementation:
- Emphasize the "why" (shared configuration, centralized management)
- Don't focus on internal class relationships unless relevant to users
- Examples Should Be Realistic:
- Use proper framework APIs (
MessagingConfigurer.create()) - Show real-world configuration scenarios
- Include method chaining patterns
- Use proper framework APIs (
Content Guidelines:
- Explain Defaults and Automation:
- Document automatic behaviors (transaction management setup)
- Explain what the framework provides out-of-the-box
- Clarify when manual configuration is needed
- Cross-Reference Related Components:
- Link to sub-modules and delegated components
- Reference configuration interfaces
- Point to related framework classes
Example Template:
/**
* Brief description of component purpose and main responsibility.
* <p>
* Detailed explanation of architectural role and how it fits in the framework.
* Main purpose is to [explain key benefit/capability].
* <p>
* [Framework integration notes - who creates it, how to access it]
* <p>
* Example usage:
* <pre>{@code
* // Realistic example using proper framework APIs
* }</pre>
*
* @author [Author Name]
* @since [version, like 5.1.0]
*/