Skip to content

[#3485] Add annotated and declarative handler interceptor support for events, commands, and queries#4515

Merged
smcvb merged 24 commits into
mainfrom
feature/3485-annotated-handler-interceptors
May 7, 2026
Merged

[#3485] Add annotated and declarative handler interceptor support for events, commands, and queries#4515
smcvb merged 24 commits into
mainfrom
feature/3485-annotated-handler-interceptors

Conversation

@abuijze
Copy link
Copy Markdown
Contributor

@abuijze abuijze commented May 2, 2026

Summary

Introduces annotated handler interceptor support for all three message types (events, commands, and queries), plus declarative interceptor registration on the command and query module builder APIs.

Annotated interceptors

Three annotations allow interceptor methods to be declared directly on a handling component class:

  • @EventHandlerInterceptor — applied before every @EventHandler method on the same instance
  • @CommandHandlerInterceptor — applied before every @CommandHandler method on the same instance
  • @QueryHandlerInterceptor — applied before every @QueryHandler method on the same instance

Two styles are supported:

Before-interceptorvoid method, no chain parameter; chain auto-proceeds after the method returns normally:

@CommandHandlerInterceptor
void audit(CommandMessage<?> command) {
    auditLog.record(command.qualifiedName());
}

Surround-interceptor — returns MessageStream, declares a MessageHandlerInterceptorChain parameter; the method controls whether and when the chain proceeds:

@CommandHandlerInterceptor
MessageStream<?> authorize(CommandMessage<?> command,
                            MessageHandlerInterceptorChain<CommandMessage<?>> chain,
                            ProcessingContext ctx) {
    if (!securityContext.isAuthorized(command)) {
        return MessageStream.failed(new AccessDeniedException("Not authorized"));
    }
    return chain.proceed(command, ctx);
}

The optional payloadType attribute narrows the interceptor to messages whose payload is assignable to a specific type.

The supporting infrastructure (MessageHandlerInterceptorDefinition, ChainedMessageHandlerInterceptorMember, InterceptorChainParameterResolverFactory, LazyMessageStream, IgnoredEntriesMessageStream) is generic and shared across all three message types.

Declarative interceptors

EventHandlingComponentsConfigurer.intercepted(), CommandHandlingModule.CommandHandlerPhase.intercepted(), and QueryHandlingModule.QueryHandlerPhase.intercepted() register a MessageHandlerInterceptor around the component assembled by the module:

CommandHandlingModule.named("orders")
        .commandHandlers()
        .autodetectedCommandHandlingComponent(cfg -> new OrderCommandHandler())
        .intercepted(cfg -> new AuthorizationInterceptor())
        .build();

Multiple calls accumulate interceptors in registration order. The assembled component is wrapped in the appropriate Intercepting*HandlingComponent when at least one interceptor is registered.

Documentation

component-message-intercepting.adoc has been rewritten: the placeholder warning is removed, all three annotation types are documented with correct Axon Framework 5 APIs, and a new section covers the declarative module-level intercepted() approach.


This PR resolves #3485.

abuijze added 4 commits May 1, 2026 14:25
Adds intercepted() to CompletePhase, enabling MessageHandlerInterceptors
to be applied to all event handling components in a configurer. Calling
intercepted() closes the component registration phase; subsequent calls
accumulate interceptors in registration order.
Adds a third interceptor registration scope to the event handler interceptors
section: per-configurer, via intercepted() on EventHandlingComponentsConfigurer.
Also fixes a typo in the existing per-processor example (withInterceptr).
@abuijze abuijze requested a review from a team as a code owner May 2, 2026 12:32
@abuijze abuijze requested review from MateuszNaKodach, hatzlj and hjohn and removed request for a team May 2, 2026 12:32
@abuijze abuijze self-assigned this May 2, 2026
@abuijze abuijze requested a review from smcvb May 2, 2026 12:34
@abuijze abuijze added this to the Release 5.2.0 milestone May 2, 2026
abuijze added 3 commits May 2, 2026 17:06
# Conflicts:
#	messaging/src/main/java/org/axonframework/messaging/core/IgnoredEntriesMessageStream.java
#	messaging/src/main/java/org/axonframework/messaging/core/MessageStream.java
Therefore, the test for interceptors must not assume that the resulting stream should be consumed before a handler is invoked.
@abuijze
Copy link
Copy Markdown
Contributor Author

abuijze commented May 3, 2026

Relates to #3485

…n support

Extends the annotated handler interceptor mechanism (already present for events)
to command and query handling components. Adds declarative interceptor support
to CommandHandlingModule and QueryHandlingModule, mirroring the existing
EventHandlingComponentsConfigurer.intercepted() API.
@abuijze abuijze changed the title [#3485] Add @EventHandlerInterceptor annotation support [#3485] Add annotated handler interceptor support for commands and queries May 3, 2026
@abuijze abuijze changed the title [#3485] Add annotated handler interceptor support for commands and queries [#3485] Add annotated handler interceptor support for events, commands, and queries May 3, 2026
@abuijze abuijze changed the title [#3485] Add annotated handler interceptor support for events, commands, and queries [#3485] Add annotated and declarative handler interceptor support for events, commands, and queries May 3, 2026
abuijze added 3 commits May 3, 2026 22:07
- 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
@smcvb smcvb added Type: Feature Use to signal an issue is completely new to the project. Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. labels May 4, 2026
smcvb added 5 commits May 4, 2026 14:01
Clean indentation

#3485
Use if-block i.o. single-line statement

#3485
Move interceptor specific classes to interceptor package

#3485
Drop 5.2.0 version, as we do not have to give updated-date versions too

#3485
Move annotation under commandhandling/interception

#3485
smcvb added 3 commits May 4, 2026 14:37
Move annotation under eventhandling/interception

#3485
Move annotation under queryhandling/interception

#3485
Drop generic on Message, as that no longer exists

#3485
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.

I am most doubtful about the use of the payloadType on the annotations. Should we still do that? Furthermore, I see an (I assume) accidental mix of #3485 and #3062 in this PR. I'd expect any @ExceptionHandler logic to be part of the other PR (#4520) that you've created. Lastly, I am missing tests for the @MessageHandlerInterceptor in this renewed setup.

Comment thread docs/reference-guide/modules/messaging-concepts/partials/nav.adoc Outdated
Comment thread docs/reference-guide/modules/events/pages/event-processors/index.adoc Outdated
abuijze added 3 commits May 4, 2026 16:29
Removed payloadType support from interceptors. Although present on handlers, it is somewhat confusing on interceptors, as they inherently address messages of different types.

Changed ordering of interceptor sections to Command -> Event -> Query.
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
Copy link
Copy Markdown
Contributor Author

abuijze commented May 5, 2026

I removed the payloadType references from the interceptors. It was misleading at best. I decided to not replace it with a messageName or anything, because interceptors are designed to be crosscutting concerns. If they apply to a specific message type, they should be part of that message's handler.
I must have accidentally committed the Exception Handler documentation on the wrong branch. Moved that out from here to #4520, where it belongs.

@abuijze abuijze requested a review from smcvb May 5, 2026 07:33
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 6, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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 4315b67 into main May 7, 2026
9 of 10 checks passed
@smcvb smcvb deleted the feature/3485-annotated-handler-interceptors branch May 7, 2026 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. 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.

Declarative and Annotated Message Handling Component Interceptor support

2 participants