The test failed about once in every ten CI runs, which is rare enough to ignore and frequent enough to hate. It covered a simple flow: an order was saved, an event was published, and an asynchronous listener sent a confirmation. The assertion at the end checked that the notification had gone out, and it sat behind a Thread.sleep(100) that had been there since the first version of the test.
Everyone had a theory, and everyone's theory was that the CI machines were slow. So the sleep went from 100 milliseconds to 500, the failure rate dropped, and the ticket was closed. Nobody asked the more useful question, which was why a listener that did almost no work needed any waiting at all.
The answer was in production, where the same flow produced a handful of "order not found" warnings per day under load. The listener was annotated with a plain @EventListener plus @Async, which means the event was handed to another thread the moment it was published, while the publishing transaction was still open. Most of the time the transaction committed before the listener thread got around to querying the order. Occasionally it did not, and the listener looked for a row that did not exist yet from any other connection's point of view. The test was flaky for the same reason production was occasionally wrong: a race between a commit on one thread and a read on another. Longer sleeps only gave the commit more time to win.
The fix had two parts. On the production side, the listener became a @TransactionalEventListener bound to the AFTER_COMMIT phase, so it does not start until the data it depends on is visible to everyone.
@Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) void on(OrderPlaced event) { notifier.send(event.orderId()); } On the test side, the sleep was replaced with Awaitility, which polls for the condition and fails with a clear message if it is not met within a limit.
await().atMost(Duration.ofSeconds(5)) .untilAsserted(() -> verify(notifier).send(orderId)); One detail cost us an hour. A test method annotated with @Transactional rolls back instead of committing, so an AFTER_COMMIT listener never fires inside it. The test that exercises this flow has to commit for real, which means it cannot lean on the usual rollback cleanup and needs to remove its own data afterwards.
Across 200 CI runs, the original test failed 22 times, the 500 millisecond version failed 6 times, and the version with the corrected listener and Awaitility failed none.
The lesson is not about Awaitility, а sleep in a test is a guess about timing, and when the guess is right only most of the time, the failures are often reporting something real about the code. Raising the timeout makes the report quieter without making it wrong.
More like this?
I write short production war stories like this one, plus a deep-dive architecture series. Subscribe here if you want both in your inbox.
Recommended Resources for Java & Spring Boot Engineers
If you are preparing for Senior/Lead Java interviews or looking to solidify your Spring Boot & Architecture skills, check out these highly-rated resources from the Javarevisited publication (Use promo code friends20 for an exclusive 20% discount automatically applied at checkout):
- Grokking the Java Interview Prepare for core Java, concurrency, JVM internals, and design pattern questions. Get Full Book (Paid) | Download Free Sample Copy
- Grokking the Spring Boot Interview Master Spring Core, Auto-configuration, Spring Data JPA, Security, and Microservices. Get Full Book (Paid) | Download Free Sample Copy
- Grokking the SQL Interview Deep-dive into query optimization, indexing, joins, and complex SQL window functions. Get Full Book (Paid) | Download Free Sample Copy
- The Complete Java + Spring + SQL Interview Bundle Get all three interview guides in a single heavily discounted package. Get the Ultimate Interview Bundle
- Spring Professional Certification Practice Questions (250+ Questions) Validating your skills? Practice with real exam-style questions before taking the Spring Professional certification. Get the Spring Professional Questions | Download Free Sample Copy

