Skip to content

[#3062] Add exception handler support for command, event, and query handling components#4520

Merged
smcvb merged 14 commits into
mainfrom
feature/3062-exception-handler-interceptors
May 13, 2026
Merged

[#3062] Add exception handler support for command, event, and query handling components#4520
smcvb merged 14 commits into
mainfrom
feature/3062-exception-handler-interceptors

Conversation

@abuijze
Copy link
Copy Markdown
Contributor

@abuijze abuijze commented May 3, 2026

Summary

  • Introduces CommandHandlingExceptionHandler and QueryHandlingExceptionHandler interfaces for intercepting exceptions thrown by command/query handlers
  • Adds ErrorHandlingCommandHandlingComponent and ErrorHandlingQueryHandlingComponent decorator implementations that wrap a delegate component and route failures through the registered exception handlers
  • Adds DelegatingCommandHandlingComponent and DelegatingQueryHandlingComponent base classes for clean delegation patterns
  • Exposes withExceptionHandler(...) on CommandHandlerPhase and QueryHandlerPhase configuration interfaces; multiple handlers chain in registration order (first registered sees the exception first)

Notes

This PR depends on feature/3485-annotated-handler-interceptors and must be merged after it. It completes exception handler support across all three message types (event handling was added in the parent commit on this branch).

abuijze added 2 commits May 3, 2026 17:46
Introduces MessageHandlingExceptionHandler and EventHandlingExceptionHandler
interfaces along with ErrorHandlingEventHandlingComponent to let callers intercept
exceptions thrown by event handlers - either suppressing them or propagating a
different error. Exposes withExceptionHandler() on EventHandlingComponentsConfigurer
to apply a handler across all registered components.

Also fixes @ExceptionHandler resolution to carry the exception through
ProcessingContext instead of a ThreadLocal, making it safe for async/reactive
dispatch paths, and adds a null-safety guard for missing interceptor chains in
MessageHandlerInterceptorDefinition.

Key changes:
- New MessageHandlingExceptionHandler / EventHandlingExceptionHandler interfaces
- New ErrorHandlingEventHandlingComponent decorator
- withExceptionHandler() on EventHandlingComponentsConfigurer
- ResultParameterResolverFactory switched from ThreadLocal to ProcessingContext-based result injection
- Null-guard in MessageHandlerInterceptorDefinition
- Tests enabled and extended, docs updated
…ts (#3062)

Introduces CommandHandlingExceptionHandler and QueryHandlingExceptionHandler interfaces,
along with ErrorHandlingCommandHandlingComponent and ErrorHandlingQueryHandlingComponent
decorators that wrap handling components to intercept handler exceptions. Exception handlers
are registered via withExceptionHandler() on the CommandHandlerPhase and QueryHandlerPhase
configuration interfaces, and multiple handlers chain in registration order.
@abuijze abuijze requested a review from a team as a code owner May 3, 2026 16:13
@abuijze abuijze requested review from MateuszNaKodach, hatzlj and smcvb and removed request for a team May 3, 2026 16:13
abuijze added 3 commits May 3, 2026 21:08
…ler branch

Resolves conflicts in CommandHandlingModule, QueryHandlingModule, and their
Simple* implementations by combining both interceptor and exception handler
support. Interceptors are applied first (inner layer), exception handlers
outermost, so exception handlers catch errors from the full chain.
- Enable component-message-intercepting and annotated-exception-handling in nav
- Replace annotated-exception-handling.adoc WARNING placeholder with real content
- Replace "not yet supported" section in migration guide with migration examples
  for @CommandHandlerInterceptor, @EventHandlerInterceptor, @QueryHandlerInterceptor,
  and @ExceptionHandler
@abuijze
Copy link
Copy Markdown
Contributor Author

abuijze commented May 4, 2026

Resolves #3062

@abuijze abuijze changed the title Add exception handler support for command and query handling components (#3062) Add exception handler support for command, event, and query handling components (#3062) May 4, 2026
@smcvb smcvb added Type: Feature Use to signal an issue is completely new to the project. Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. labels May 4, 2026
@smcvb smcvb added this to the Release 5.2.0 milestone May 4, 2026
- Enable exceptionHandlerAnnotatedMethodsAreSupportedForCommandHandling-
  Components test; rework to given/when/then style in AnnotatedInterceptor-
  Handling and verify the exception handler is invoked
- Add exceptionHandlerWithChainParamIsRejected tests for command and
  query handler components (equivalent to the event handler test)
- Fix the entity-constructor NOTE in component-message-intercepting.adoc
  to reflect that AF5 uses static creation handlers, not constructors
- Fix CommandMessage<?> to CommandMessage (no generic) in the
  @ExceptionHandler migration guide prose
abuijze added a commit that referenced this pull request May 4, 2026
Remove the @ExceptionHandler test, documentation, and migration guide
content that belongs in the @ExceptionHandler PR (#3062/#4520), not
in the annotated interceptors PR (#3485/#4515).

Also add missing @NullMarked package-info.java files for the three
new annotation packages introduced for annotated interceptors.
abuijze added 3 commits May 4, 2026 17:40
…xception-handler-interceptors

Preserve @ExceptionHandler documentation that was erroneously removed on the 3485 branch:
- Restored annotated-exception-handling.adoc
- Kept @ExceptionHandler section in component-message-intercepting.adoc
- Kept @ExceptionHandler migration section in interceptors.adoc
- Kept active nav entry for annotated-exception-handling
- Kept null-safety guard in MessageHandlerInterceptorDefinition

From 3485: move interceptor annotations to interception/annotation sub-packages,
add package-info.java files, drop Message<?> generics, cleanup from review comments.
payloadType filtering is already achievable by declaring the payload as a method parameter, matching how @MessageHandlerInterceptor works.
Exception handlers are designed to deal with cross-cutting concerns and should therefore not rely on a specific payload. They can, however, take the entire message as a parameter, which also restricts them to handle only exceptions caused by that type of message.
Base automatically changed from feature/3485-annotated-handler-interceptors to main May 7, 2026 07:43
Copy link
Copy Markdown
Contributor

@smcvb smcvb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bunch of naming concerns, wondering why we make new Message Handling Components for every registered exception handler (as this deviates from interceptor support, which it aligns with closely), but most importantly, I am wondering if we need a dedicated decorating MessageHandlingComponent for exception handling. Can't we have the declarative exception registration approach build on the declarative interception approach, as it is "just another interceptor" in the ned?> I'd wager that would limit custom code for the interceptors quite a bit.

* @param exceptionHandler the exception handler to apply to all components
* @return this phase for further configuration
*/
default CompletePhase withExceptionHandler(EventHandlingExceptionHandler exceptionHandler) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we only register one exception handler? That doesn't seem entirely correct to me. From an annotation perspective, users can also define several, dealing with different exceptions if needed. I believe the declarative approach currently misses that capability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can register as many as you want. Just repeat the invocation

* @param exceptionHandler the exception handler to apply to the query handling component
* @return this phase for further configuration
*/
QueryHandlerPhase withExceptionHandler(QueryHandlingExceptionHandler exceptionHandler);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new method is a breaking change right now for implementers of this interface. I think we should have a default here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we the only implementers of this interface? Or put differently: do we ever expect anyone to implement this interface, and wouldn't a breaking API change then be better than a silent failure when a default implementation is unexpectedly invoked?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I, indeed, don't expect users of Axon Framework to implement this. We just may see implementations in different repositories maintained by Axoniq, though.

We've already recently seen this between the Framework and Platform team. I similarly thought "what harm does it do to change some of these methods between 5.0 and 5.1." Well, I can tell you, the Platform team was harmed by that quite a bit.

If you feel strongly we need to make an exception here, I'll let it go. I mean, I get your POV. But I re-reverted my stance to "let's just not do this" after letting it slide between 5.0 and 5.1.

Comment thread docs/reference-guide/modules/migration/pages/paths/interceptors.adoc Outdated
…ndling

The declarative withExceptionHandler API on CommandHandlingModule,
QueryHandlingModule, and EventHandlingComponentsConfigurer now delegates
to the existing interceptor mechanism rather than introducing separate
decorator component types. The method is defined as a default method on
each interface, wrapping the exception handler as a MessageHandlerInterceptor
via intercepted(). Parameters changed to ComponentBuilder<*ExceptionHandler>
to align with the existing intercepted() API style.

As a result, the ErrorHandling*HandlingComponent classes and the now-unused
Delegating*HandlingComponent base classes are removed. The ordering semantics
change accordingly: later-registered exception handlers are innermost
(closest to the handler) and see exceptions first, consistent with how
interceptor chains work.

Also fixes the migration guide to note that the @ExceptionHandler payloadType
attribute was removed in Axon Framework 5.
@abuijze abuijze requested a review from smcvb May 11, 2026 12:02
@smcvb smcvb changed the title Add exception handler support for command, event, and query handling components (#3062) [#3062] Add exception handler support for command, event, and query handling components May 13, 2026
Copy link
Copy Markdown
Contributor

@smcvb smcvb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not approving nor requesting changes as I am slightly unsure whether my assumption is right that we cannot receive generic Message exception handlers in the declarative approach.

* @return this phase for further configuration
*/
default CommandHandlerPhase withExceptionHandler(
ComponentBuilder<CommandHandlingExceptionHandler> handlerBuilder) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this also allow us to register a generic exception handler that can deal with any message? I believe the CommandHandlingExceptionHandler now blocks us from doing this, as it expects a CommandMessage exactly. This is where the declarative approach now deviates from the annotated approach, wherein we can provide users the means to provide a generic exception handler.

Or, if I am wrong here, feel free to disregard my comment.

* @param handlerBuilder builder for the exception handler to apply to all components
* @return this phase for further configuration
*/
default CompletePhase withExceptionHandler(ComponentBuilder<EventHandlingExceptionHandler> handlerBuilder) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar "can we receive generic exception handlers?"-concern as placed on the command handling module.

* @param handlerBuilder builder for the exception handler to apply to the query handling component
* @return this phase for further configuration
*/
default QueryHandlerPhase withExceptionHandler(ComponentBuilder<QueryHandlingExceptionHandler> handlerBuilder) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar "can we receive generic exception handlers?"-concern as placed on the command handling module.

Relax the parameter on CommandHandlerPhase, CompletePhase, and
QueryHandlerPhase withExceptionHandler methods to accept any
MessageHandlingExceptionHandler typed at a supertype of the phase's
message type. A single generic handler (e.g. a logging exception
handler) can now be registered against any of the three phases
without retyping per message kind. Existing typed-subinterface
registrations continue to compile unchanged.
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@smcvb smcvb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concerns have been addressed, hence I'm approving this pull request.

@smcvb smcvb merged commit 4186b8a into main May 13, 2026
9 checks passed
@smcvb smcvb deleted the feature/3062-exception-handler-interceptors branch May 13, 2026 15:02
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. Type: Feature Use to signal an issue is completely new to the project.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants