An application event delivers a notification to listeners registered in the current application context.
Spring application events: local delivery is not a durable queue
This lesson uses the downloadable source kit: Java 21, Spring Boot 4.0.8 and its managed Spring Framework 7 dependencies. The version is pinned for repeatable builds.
Keep the delivery promise small
ContainerContracts publishes receipt-created and its ordinary listener increments a counter before publishEvent returns. This local fixture verifies synchronous default delivery. A listener that performs slow I/O would therefore delay the publisher unless delivery is configured differently.
An event name does not persist work. If the JVM stops after a payment commits but before a notification completes, this local counter cannot replay the notification. A business promise of eventual delivery needs durable state and an idempotent consumer.
Transaction timing is another boundary
A regular listener can run before a surrounding database transaction commits. Reading the event as proof of committed data is therefore unsafe. Transaction-bound listeners require their own phase and rollback tests; they are a separate extension not implemented by this fixture.
The kit uses a string payload to expose delivery timing. A real event should carry a stable identifier and only the data its consumer needs. Passing a mutable entity graph into an asynchronous listener creates ownership and persistence-lifetime problems.
Checked source
AtomicInteger messages = new AtomicInteger();
context.addApplicationListener((ApplicationListener<PayloadApplicationEvent<String>>) event -> messages.incrementAndGet());
context.publishEvent("receipt-created");
System.out.println(messages.get());Test the boundary
Run mvn test in the source-kit directory. CoreBoundaryTest checks the behavior described here. Java excerpts belong to the named source-kit classes; they are not independent source files unless the complete class is shown.
Costs and boundaries
Publisher cost includes synchronous listener work in this fixture. Async delivery adds queue capacity and cancellation costs; neither form provides broker persistence.
Common Mistakes
- A local event is not durable messaging.
- A published event is not proof that a database transaction committed.
- Do not transfer mutable request state without an ownership rule.
Read next
Transactions, Async boundaries, Java bounded executors: test saturation and rejected work.
Extend this boundary
Continue with Spring transaction events: run a listener after commit without claiming durability.
