Thirteen captured transcripts: a listener is a blocking method call, ordering and chaining, SpEL conditions, generic-event erasure, every transaction phase on commit and rollback, what an AFTER_COMMIT listener can write, which exceptions reach the publisher, async listeners on platform and virtual threads, and the annotations behind Spring Modulith's @ApplicationModuleListener. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
core-events
Companion project for the article Spring Application Events: @EventListener, @TransactionalEventListener and Async Events on ankurm.com.
Every console block quoted in the article came out of output/. Transcripts 01-11 are written by the test
suite (so a claim that stops being true turns the build red); 12-13 are read out of jars by
scripts/capture-facts.sh.
There is deliberately no docs/ folder: the deeper material lives in collapsible "going deeper"
sections inside the article itself, next to the paragraph each one extends.
Versions
| Spring Boot | 4.1.1 |
| Spring Framework | 7.0.9 |
| JDK | 25 (Temurin 25.0.4.1+1) |
| Maven | 3.9 |
| H2 | the version Boot 4.1.1 manages |
| Spring Modulith (javap only) | 2.1.1 |
Quickstart
export JAVA_HOME=/path/to/jdk-25
mvn test # runs the scenarios and rewrites output/01-11
./scripts/run-all.sh # everything, including 12-13 (needs network access to Maven Central once)
Source layout
Each scenario is its own package with its own @SpringBootApplication, which scans only that package, so a
scenario starts with exactly the beans it shows and nothing else.
| Package | What it holds |
|---|---|
basics/ |
OrderService publishes an OrderPlaced record; two listeners; the publisher blocks |
ordering/ |
@Order across two beans, an event returned from a listener, a listener that throws |
conditional/, badcondition/ |
SpEL conditions, and one that names a property that does not exist |
generics/ |
Created<T> and its erasure problem, and ResolvableTypeProvider |
tx/ |
one listener per transaction phase, a publisher that commits or rolls back, fallbackExecution |
txwrite/ |
an AFTER_COMMIT listener that writes: nothing declared, REQUIRES_NEW, plain @Transactional |
asyncdemo/ |
@Async listeners, the thread they land on, and what they see of the publisher's transaction |
asyncplain/ |
the same @Async listener in an application that forgot @EnableAsync |
support/ |
Trace (the in-memory log the tests print), Modes, and Visibility (asks "could another connection see this row?") |
Captured output
| File | What it shows |
|---|---|
01-publish-is-a-method-call.txt |
a listener runs on the publisher's thread, before publishEvent returns |
02-order-and-chaining.txt |
@Order across beans, an event returned from a listener, and a throwing listener stopping the rest |
03-conditional-listeners.txt |
which SpEL-conditioned listeners ran for four events; the failure for a misspelled property |
04-generic-events-and-erasure.txt |
a generic event that reaches no listener, and the ResolvableTypeProvider fix |
05-transaction-phases-commit-and-rollback.txt |
every phase for a commit and for a rollback, with row visibility from another connection |
06-no-transaction-drops-the-event.txt |
publishing outside a transaction, and fallbackExecution |
07-after-commit-writes.txt |
an AFTER_COMMIT listener that inserts, declared three ways |
08-exceptions-and-transactions.txt |
which listener's exception reaches the caller, and what it does to the row |
09-async-listener-threads.txt |
an @Async listener on a platform thread, on a virtual thread, and with @EnableAsync forgotten |
10-async-and-the-transaction.txt |
what an async listener can see of the publisher's transaction |
11-async-exceptions.txt |
an async listener's exception: not the publisher's problem, but logged |
12-modulith-application-module-listener.txt |
the annotations @ApplicationModuleListener is made of |
13-transaction-cleanup-calls.txt |
the calls DataSourceTransactionManager makes when it cleans up |