Basic information
- Axon Framework version:
5.1.1-SNAPSHOT (also axoniq-spring-boot-starter:5.1.1-SNAPSHOT, axon-spring-boot-starter:5.1.1-SNAPSHOT from org.axonframework.extensions.spring)
- JDK version: 23
- Spring Boot version: 3.5.14
- Complete executable reproducer:
MateuszNaKodach/HeroesOfDomainDrivenDesign.EventSourcing.Java.Axon.Spring, branch af5-migration/test1. Relevant commits:
Steps to reproduce
- Spring Boot 3.5 application on
axoniq-spring-boot-starter:5.1.1-SNAPSHOT with Hibernate JPA + Postgres (or any JPA provider), axon.axonserver.enabled: false, ddl-auto: update.
- Declare an explicit
@Bean EventStorageEngine to deterministically select AggregateBasedJpaEventStorageEngine over the axon-server-connector's ServiceLoader-registered default (this is necessary whenever the connector is on the classpath — see "Why the user bean is necessary" below):
@Configuration
public class EventStoreConfiguration {
@Bean
public EventStorageEngine eventStorageEngine(EntityManagerFactory emf,
EventConverter eventConverter) {
return new AggregateBasedJpaEventStorageEngine(
new JpaTransactionalExecutorProvider(emf),
eventConverter,
UnaryOperator.identity()
);
}
}
- Do not add any
@EntityScan to the @SpringBootApplication class. Rely on AF5's @RegisterDefaultEntities mechanism to register AggregateEventEntry.
- Run a
@SpringBootTest that exercises the JPA event store (any test that boots the full context against a fresh Postgres — Testcontainers will do).
The application context fails to load. Stack trace shows:
org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'AggregateEventEntry'
Expected behaviour
AggregateEventEntry should be registered with the application's EntityManagerFactory via the AF5 @RegisterDefaultEntities(packages = {"org.axonframework.eventsourcing.eventstore.jpa"}) declaration on JpaEventStoreAutoConfiguration — the mechanism explicitly documented to make user-side @EntityScan for framework JPA entities unnecessary. The user supplying their own EventStorageEngine should not affect entity discovery: the engine bean and the persistence-metamodel registration are orthogonal concerns.
Actual behaviour
JpaEventStoreAutoConfiguration carries both of these annotations:
@ConditionalOnMissingBean(value = {EventStore.class, EventStorageEngine.class})
@RegisterDefaultEntities(packages = {
"org.axonframework.eventsourcing.eventstore.jpa"
})
public class JpaEventStoreAutoConfiguration { ... }
(extensions/spring/spring-boot-autoconfigure/src/main/java/org/axonframework/extension/springboot/autoconfig/JpaEventStoreAutoConfiguration.java, lines 61–65.)
Spring's ConfigurationClassParser evaluates class-level @Conditional annotations before processing @Import. As soon as the user defines a @Bean EventStorageEngine, the class-level @ConditionalOnMissingBean(EventStorageEngine.class) evaluates to false and the entire @AutoConfiguration class is filtered out — including its @Import(DefaultEntityRegistrar.class) (declared by @RegisterDefaultEntities, see …/util/RegisterDefaultEntities.java). The DefaultEntityRegistrar.registerBeanDefinitions(...) callback never runs, so AutoConfigurationPackages.register(registry, "org.axonframework.eventsourcing.eventstore.jpa") is never called, so Spring Boot's JpaBaseConfiguration.getPackagesToScan() never sees the framework package, so Hibernate's metamodel never picks up AggregateEventEntry.
The bug cascades as soon as the user reaches for the obvious workaround. Adding @EntityScan(basePackages = "org.axonframework.eventsourcing.eventstore.jpa") recovers AggregateEventEntry, but the next test run fails with:
org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'TokenEntry'
JpaAutoConfiguration (.../JpaAutoConfiguration.java, lines 55–57) was registering TokenEntry via its own @RegisterDefaultEntities. That registration is silently dropped because Spring Boot's JpaBaseConfiguration.getPackagesToScan() prefers EntityScanPackages (from @EntityScan) over AutoConfigurationPackages when an @EntityScan is declared. The user must then keep widening @EntityScan until it covers every framework JPA package that ships an entity. AxoniqFramework's JpaDeadLetterQueueAutoConfiguration for io.axoniq.framework.messaging.eventhandling.deadletter.jpa (DeadLetterEntry) is dropped by the same displacement.
End state observed in the reproducer: the user's @SpringBootApplication carries
@EntityScan(basePackages = {
"com.dddheroes.heroesofddd",
"org.axonframework",
"io.axoniq.framework"
})
to recover green tests. This is the minimum that covers AggregateEventEntry, TokenEntry, and DeadLetterEntry, and must be widened again whenever a new framework module ships a JPA entity. The @RegisterDefaultEntities mechanism is, in this configuration, entirely non-functional.
Failure trail (./mvnw test -Dtest='GetDwellingByIdTest' at commit 77d5ea3)
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [com.dddheroes.heroesofddd.utils.AggregateEventPublisher ...] — JUnit error masking the real failure (constructor autowiring disabled because @TestConstructor default mode is ANNOTATED). Fix via src/test/resources/spring.properties with spring.test.constructor.autowire.mode=all.
org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'AggregateEventEntry' — the root issue described here.
- After
@EntityScan(basePackages = "org.axonframework.eventsourcing.eventstore.jpa"): Could not resolve root entity 'TokenEntry'.
- After expanding
@EntityScan to cover org.axonframework and io.axoniq.framework: green.
Why the user bean is necessary
Without an explicit @Bean EventStorageEngine, axon-server-connector's ServiceLoader-discovered AxonServerConfigurationEnhancer (order Integer.MIN_VALUE + 10) registers AxonServerEventStorageEngine via registerIfNotPresent(..., SearchScope.ALL) before JpaEventStoreAutoConfiguration's enhancer (order ≈ Integer.MAX_VALUE - 600) gets a chance — even with axon.axonserver.enabled=false, which only gates the Spring @Bean methods on AxonServerAutoConfiguration, not the ServiceLoader-discovered enhancer. The user-provided Spring @Bean is the only deterministic way to make JPA win, via SpringComponentRegistry.hasComponent(..., SearchScope.ALL) consulting the Spring BeanFactory before either enhancer runs. So the entity-registration coupling bites exactly the users who need the explicit @Bean most.
Notes
I'm not sure this is cleanly fixable on the framework side — moving @RegisterDefaultEntities off the engine-gated autoconfig would register AggregateEventEntry for users running pure Axon Server (where the entity table is schema pollution they don't want). The current coupling may be intentional, gated by @ConditionalOnMissingBean to express "register entities only when this autoconfig is going to provide the engine".
If it can't be fixed, the minimum-viable outcome is to document this in the AF4 → AF5 migration paths for the event-storage-engine: whenever the migration recipe instructs the user to declare an explicit @Bean EventStorageEngine (Path A on axoniq-spring-boot-starter, or any Path B variant), pair that instruction with a note that @EntityScan(basePackages = {<app-package>, "org.axonframework", "io.axoniq.framework"}) is also required, and warn that a partial @EntityScan silently disables @RegisterDefaultEntities contributions from sibling autoconfigs. That doc-only outcome is acceptable.
Basic information
5.1.1-SNAPSHOT(alsoaxoniq-spring-boot-starter:5.1.1-SNAPSHOT,axon-spring-boot-starter:5.1.1-SNAPSHOTfromorg.axonframework.extensions.spring)MateuszNaKodach/HeroesOfDomainDrivenDesign.EventSourcing.Java.Axon.Spring, branchaf5-migration/test1. Relevant commits:77d5ea3— declared the explicit@Bean EventStorageEnginethat triggers the bug.d3319d9—@EntityScanworkaround.3ecac02— full write-up in.axon4-to-axon5-migration/learnings.mdunder2026-05-12 — Phase 9 follow-up.Steps to reproduce
axoniq-spring-boot-starter:5.1.1-SNAPSHOTwith Hibernate JPA + Postgres (or any JPA provider),axon.axonserver.enabled: false,ddl-auto: update.@Bean EventStorageEngineto deterministically selectAggregateBasedJpaEventStorageEngineover theaxon-server-connector's ServiceLoader-registered default (this is necessary whenever the connector is on the classpath — see "Why the user bean is necessary" below):@EntityScanto the@SpringBootApplicationclass. Rely on AF5's@RegisterDefaultEntitiesmechanism to registerAggregateEventEntry.@SpringBootTestthat exercises the JPA event store (any test that boots the full context against a fresh Postgres — Testcontainers will do).The application context fails to load. Stack trace shows:
Expected behaviour
AggregateEventEntryshould be registered with the application'sEntityManagerFactoryvia the AF5@RegisterDefaultEntities(packages = {"org.axonframework.eventsourcing.eventstore.jpa"})declaration onJpaEventStoreAutoConfiguration— the mechanism explicitly documented to make user-side@EntityScanfor framework JPA entities unnecessary. The user supplying their ownEventStorageEngineshould not affect entity discovery: the engine bean and the persistence-metamodel registration are orthogonal concerns.Actual behaviour
JpaEventStoreAutoConfigurationcarries both of these annotations:(
extensions/spring/spring-boot-autoconfigure/src/main/java/org/axonframework/extension/springboot/autoconfig/JpaEventStoreAutoConfiguration.java, lines 61–65.)Spring's
ConfigurationClassParserevaluates class-level@Conditionalannotations before processing@Import. As soon as the user defines a@Bean EventStorageEngine, the class-level@ConditionalOnMissingBean(EventStorageEngine.class)evaluates tofalseand the entire@AutoConfigurationclass is filtered out — including its@Import(DefaultEntityRegistrar.class)(declared by@RegisterDefaultEntities, see…/util/RegisterDefaultEntities.java). TheDefaultEntityRegistrar.registerBeanDefinitions(...)callback never runs, soAutoConfigurationPackages.register(registry, "org.axonframework.eventsourcing.eventstore.jpa")is never called, so Spring Boot'sJpaBaseConfiguration.getPackagesToScan()never sees the framework package, so Hibernate's metamodel never picks upAggregateEventEntry.The bug cascades as soon as the user reaches for the obvious workaround. Adding
@EntityScan(basePackages = "org.axonframework.eventsourcing.eventstore.jpa")recoversAggregateEventEntry, but the next test run fails with:JpaAutoConfiguration(.../JpaAutoConfiguration.java, lines 55–57) was registeringTokenEntryvia its own@RegisterDefaultEntities. That registration is silently dropped because Spring Boot'sJpaBaseConfiguration.getPackagesToScan()prefersEntityScanPackages(from@EntityScan) overAutoConfigurationPackageswhen an@EntityScanis declared. The user must then keep widening@EntityScanuntil it covers every framework JPA package that ships an entity. AxoniqFramework'sJpaDeadLetterQueueAutoConfigurationforio.axoniq.framework.messaging.eventhandling.deadletter.jpa(DeadLetterEntry) is dropped by the same displacement.End state observed in the reproducer: the user's
@SpringBootApplicationcarriesto recover green tests. This is the minimum that covers
AggregateEventEntry,TokenEntry, andDeadLetterEntry, and must be widened again whenever a new framework module ships a JPA entity. The@RegisterDefaultEntitiesmechanism is, in this configuration, entirely non-functional.Failure trail (
./mvnw test -Dtest='GetDwellingByIdTest'at commit77d5ea3)org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [com.dddheroes.heroesofddd.utils.AggregateEventPublisher ...]— JUnit error masking the real failure (constructor autowiring disabled because@TestConstructordefault mode isANNOTATED). Fix viasrc/test/resources/spring.propertieswithspring.test.constructor.autowire.mode=all.org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'AggregateEventEntry'— the root issue described here.@EntityScan(basePackages = "org.axonframework.eventsourcing.eventstore.jpa"):Could not resolve root entity 'TokenEntry'.@EntityScanto coverorg.axonframeworkandio.axoniq.framework: green.Why the user bean is necessary
Without an explicit
@Bean EventStorageEngine,axon-server-connector's ServiceLoader-discoveredAxonServerConfigurationEnhancer(orderInteger.MIN_VALUE + 10) registersAxonServerEventStorageEngineviaregisterIfNotPresent(..., SearchScope.ALL)beforeJpaEventStoreAutoConfiguration's enhancer (order ≈Integer.MAX_VALUE - 600) gets a chance — even withaxon.axonserver.enabled=false, which only gates the Spring@Beanmethods onAxonServerAutoConfiguration, not the ServiceLoader-discovered enhancer. The user-provided Spring@Beanis the only deterministic way to make JPA win, viaSpringComponentRegistry.hasComponent(..., SearchScope.ALL)consulting the SpringBeanFactorybefore either enhancer runs. So the entity-registration coupling bites exactly the users who need the explicit@Beanmost.Notes
I'm not sure this is cleanly fixable on the framework side — moving
@RegisterDefaultEntitiesoff the engine-gated autoconfig would registerAggregateEventEntryfor users running pure Axon Server (where the entity table is schema pollution they don't want). The current coupling may be intentional, gated by@ConditionalOnMissingBeanto express "register entities only when this autoconfig is going to provide the engine".If it can't be fixed, the minimum-viable outcome is to document this in the AF4 → AF5 migration paths for the event-storage-engine: whenever the migration recipe instructs the user to declare an explicit
@Bean EventStorageEngine(Path A onaxoniq-spring-boot-starter, or any Path B variant), pair that instruction with a note that@EntityScan(basePackages = {<app-package>, "org.axonframework", "io.axoniq.framework"})is also required, and warn that a partial@EntityScansilently disables@RegisterDefaultEntitiescontributions from sibling autoconfigs. That doc-only outcome is acceptable.