Often we need a synchronous event mechanism for hook-like flows, where listeners are expected to modify the event state — for example, authorizing a request or halting a flow, similar to event.preventDefault() in the DOM.

Spring’s event system can handle that, but neither the event publisher nor the listener author can reliably know whether an event will be processed synchronously or asynchronously. The behavior depends on the configured ApplicationEventMulticaster, so there’s no clear contract.

As far as I know, recent Spring versions addressed this for @TransactionalEventListener, which guarantees synchronous dispatch within a transaction. However, there doesn’t seem to be an equivalent general-purpose synchronous contract.

To achieve this, we currently have to define custom publisher/dispatcher beans and our own listener registration logic. I’m wondering if there’s an established best practice or recommended pattern for this use case that I may have missed.

This is especially important for library and framework authors, who often rely on event hooks to extend or modify behavior. In such cases, the library must be able to safely assume deterministic, synchronous execution.

Would it make sense for Spring to provide something like a SyncApplicationEventPublisher and a @SyncEventListener annotation for deterministic synchronous dispatch?

Comment From: jhoeller

We actually got ApplicationListener#supportsAsyncExecution() since 6.1, which is also what @TransactionalEventListener is based on (returning false there). Granted, that's a programmatic contract - but we could easily expose it for @EventListener through a corresponding annotation attribute as well, for example: @EventListener(asyncAllowed = false) which could also be meta-annotated on a custom @SyncEventListener annotation then. Would this meet your needs?

Comment From: ckalan

@jhoeller Thanks! I just checked the code and it is really a great improvement. I wasn't aware of that exact change. I just tried with a @SyncEventListener and a custom listener factory which works great so asyncAllowed=false may not be necessary anyway. The only missing part is, publisher still can't ensure the event is processed synchronously but I guess that'd be more complicated because the contract would be implied by the event not the listener.

Comment From: ckalan

I thought about this a bit more, and I realize my earlier line of thinking conflicts with the fire-and-forget nature of the ApplicationEventPublisher. So this probably isn’t the publisher’s responsibility. Instead, what makes sense is a separate component that guarantees a synchronous request-response pattern. I think I can just define a dedicated bean that collects only the synchronous event listeners and dispatches directly to them.

I appreciate your feedback. Thanks again.