Skip to content

EntityNotFoundException behavior around @EntityCreator usage for Entity-centric and Stateful-Handler-based instance command handling#4609

Open
smcvb wants to merge 20 commits into
axon-5.1.xfrom
bug/entity-not-found-exception
Open

EntityNotFoundException behavior around @EntityCreator usage for Entity-centric and Stateful-Handler-based instance command handling#4609
smcvb wants to merge 20 commits into
axon-5.1.xfrom
bug/entity-not-found-exception

Conversation

@smcvb

@smcvb smcvb commented May 22, 2026

Copy link
Copy Markdown
Contributor

This pull request is under discussion on purpose, as the team is not fully aligned yet on the direction.
What it provides, is a new test suite in the axon-integrationtest module.
The test includes 6 different entity+command handling+creation approaches:

  1. Entity-centric, no-arg entity creator
  2. Entity-centric, id-based entity creator
  3. Entity-centric, first-event-based entity creator
  4. Stateful-command-handler-centric, no-arg entity creator
  5. Stateful-command-handler-centric, id-based entity creator
  6. Stateful-command-handler-centric, first-event-based entity creator

For all 6 styles, we have two tests:

  1. Dispatch creational command for the entity - should succeed always
  2. Dispatch instance command for the entity - fails for entity-centric, succeeds for stateful-command-handler-centric.

To ensure the above is true, I made some minor changes, which are:

  1. Introduce the EntityNotFoundException, akin to the AggregateNotFoundException. This can be thrown when applicable.
  2. Adjust the AnnotationBasedEventSourcedEntityFactory to throw an EntityNotFoundException when there is no initial event, regardless of the entity-creator choice.
  3. Adjust the InjectEntityParameterResolver to catch the EntityNotFoundException to return null for the entity when it couldn't be found.

What's up to debate, and why this is still in under discussion, is whether we agree with the (1) test domains and (2) instance command handler outcomes per test.

smcvb and others added 20 commits April 3, 2026 18:04
…utionException

If the EntityCommandHandlingComponent is unable to find an entity-id when handling a command, we may be dealing with a creational command handler. Hence, we should invoke EntityMetamodel#handleCreate. If that's successful, our guess was correct. If this call returns a NoHandlerForCommandException, we were not dealing with a creational command handler at all. In that case, we should return the original EntityIdResolutionException

#4362
When the EventStoreTransaction#appendEvent is invoked and we have tags without the existence of an AppendCondition, that signals a scenario where a creational command handler is invoked to append an event without any preceding source invocation. Thus, we should be able to assume we are dealing with the first event in this case. Making an ORIGIN marker with the found tags is restrictive, but should suffice in this case.

#4362
Adjust tests to not have a resolvable identifier for creation. Although this means a shift, there's arguably more value in testing the suggested approach (to not have identifiers to load an entity when the command handler is static) then the other way around. This shift has shown the test cases to inconsistently set creational command handlers. Furthermore, the custom EntityIdResolver does not align. Lastly, the validation changes to an AppendEventsTransactionRejectedException for duplication, as the appendCondition is violated in these cases

#4362
- Resolve nullability of getting a resource
- Clarify updating a resource may mean you need to deal with null
- Fix ugly indentation as a result of non-null/nullability settings
- Clarify the ManagedEntity may not find an entity to load

#4362
# Conflicts:
#	eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/DefaultEventStoreTransaction.java
Changes to merge with axon-5.1.x

#4362
Fix assetion to correclty validate the exception

#4362
Use computeResourceIfAbsent

#4362
Add the EntityMetamodel#CREATE_WITHOUT_LOAD resource key of type boolean. This resource should be set whenever the EntityCommandHandlingComponent decided to invoked EntityMetamodel#handleCreate **without** trying to load an entity first. This is important for the DefaultEventStoreTransaction to derive whether it should default the AppendCondition. This fine-tunes the support of the DefaultEventStoreTransaction that defaulted the AppendCondition for any set of tags, to only do this whenever a creational-command-handler is triggered.

#4362
…Condition is defaulted

Set EntityMetamodel#CREATE_WITHOUT_LOAD in test validating the AppendCondition is defaulted

#4362
Co-authored-by: Jakob Hatzl <hatzlj@users.noreply.github.com>
…entstore/DefaultEventStoreTransaction.java

Co-authored-by: Jakob Hatzl <hatzlj@users.noreply.github.com>
Drop redundant first() call

#4362
Remove resource when we're failing

#4362
# Conflicts:
#	modelling/src/main/java/org/axonframework/modelling/entity/EntityMetamodel.java
#	modelling/src/test/java/org/axonframework/modelling/entity/EntityCommandHandlingComponentTest.java
Add integration tests validating all entity-creator options for creational and instance command handler behavior. This means there are 12 tests against a sample domain (GiftCard), each using their unique domain implementation for testing purposes. As such there are 6 integrations of the GiftCard, being no-arg, id-based, and first-event-based entity creators, for (1) entity-centric and (2) stateful-command-handler-based command model handling
Introduce the EntityNotFoundException. This exception should be thrown whenever we are unable to find an entity. This essentially reflects the old AggregateNotFoundException from past framework installations. It should be thrown whenever a command handler cannot discover the entity to handle the command on correctly.
Adjust annotation-based EntityFactory to throw EntityNotFoundExceptions. By doing so, we ensure that the no-arg, id-based, and event-based @EntityCreator annotated field invocations fail with an EntityNotFoundExceptions when no previous event was found. Or differently put, if the entity's event stream didn't exist yet,
Catch EntityNotFoundException to return null. By doing so, an @InjectEntity annotated parameter may return null, giving the responsibility to the user to validate if the entity already exists for a given command handler.
@smcvb smcvb added this to the Release 5.1.2 milestone May 22, 2026
@smcvb smcvb self-assigned this May 22, 2026
@smcvb smcvb requested a review from a team as a code owner May 22, 2026 14:34
@smcvb smcvb requested review from hjohn and jangalinski and removed request for a team May 22, 2026 14:34
@smcvb smcvb added the Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. label May 22, 2026
@smcvb smcvb added the Status: Under Discussion Use to signal that the issue in question is being discussed. label May 22, 2026
@laura-devriendt-lemon

laura-devriendt-lemon commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

My read on this issue

What I think works really well

Entity-centric instance commands throwing EntityNotFoundException when the entity was never created feels exactly right. You can't run a method "on" something that doesn't exist yet, and it stays consistent across all three creator styles.

The part I'm trying to understand

For stateful (@InjectEntity) handlers, the same "command before the entity exists" situation behaves differently depending on the creator style:

Creator style Injected entity when not yet created
no-arg non-null, empty
id-based non-null, id already filled in
event-based null

The no-arg and id-based handlers get a non-null entity even though the entity was never created, because those constructors don't need an event, so the framework can build one. In plain Java that is logical, but from a user's point of view it feels surprising: you ask for an entity that was never created and get back a real-looking instance instead of "nothing".

The id-based case is the one I find most confusing. Because the id is already set, I couldn't find a reliable way for the handler to tell that the entity was never created

The docs also present the entity-centric and stateful styles as basically interchangeable, so I wasn't sure users would expect them to diverge here.

A direction I'd suggest

Treat "no events yet means the entity doesn't exist" the same way for every creator style and every handler style:

  • @InjectEntity GiftCard card (the normal case): if it doesn't exist, the framework reports EntityNotFoundException for you, so you can't accidentally act on a blank one.
  • @InjectEntity @Nullable GiftCard card: you get null when it doesn't exist, so you can do create-or-update yourself.

That gives one existence rule everywhere, independent of which constructor the entity uses.

Pros and cons of that approach

Pros Cons
One rule everywhere. The "does it exist?" check is identical for all creator styles and both handler styles. It is a behavior change on released code (5.1 is GA), so it would target 5.2 and needs a migration note.
Removes the case where a command on a never-created entity silently succeeds. Handlers that today expect a non-null empty entity need a one-line tweak: mark the parameter @Nullable and check entity == null instead of a field.
The common case needs no null check and can't go wrong, because the framework guards it. It needs new framework support (reading @Nullable on the parameter).
"May not exist" becomes explicit in the handler signature via @Nullable. It is a bigger change than this PR, since it touches how the entity is loaded, so there is more to review.

There may well be reasons this direction is not feasible that I'm not aware of, and I realize it would be a larger redesign that needs more analysis. I mainly wanted to share my initial thoughts early and hear how you see it.

laura-devriendt-lemon

This comment was marked as outdated.

@smcvb smcvb modified the milestones: Release 5.1.2, Release 5.2.0 Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. Status: Under Discussion Use to signal that the issue in question is being discussed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants