# awaitility Companion code for the Awaitility article on [ankurm.com](https://ankurm.com). All intermediate and reference-grade depth lives in the post itself (accordions / "going deeper" paragraphs), not in a `docs/NN-topic.md` chapter folder — the one exception, as in the rest of this repository, is `docs/output/`, which holds real captured transcripts and nothing else. ## What this is Six real, runnable demonstrations of the same idea: a fixed `Thread.sleep(...)` in a test is a guess about timing the test does not control, and `Awaitility.await()` replaces the guess with a bounded poll loop. Each demonstration targets a different source of asynchrony: | Test | What it waits for | |---|---| | `AwaitAsyncConfirmationTest` | A `void` `@Async` method with no `Future` to block on | | `DefaultTimingTest` | Nothing — it proves Awaitility's own defaults (10s timeout, 100ms poll interval) against the real jar | | `KafkaListenerAwaitTest` | An `@KafkaListener` consuming a record a `KafkaTemplate` just sent, against a real in-process `@EmbeddedKafka` broker | | `ScheduledJobAwaitTest` | Three more executions of an already-running `@Scheduled(fixedRate = 150)` job | | `UncaughtExceptionHandlerSwapTest` | Nothing — it proves `await()` temporarily installs its own `Thread.setDefaultUncaughtExceptionHandler` and restores the original afterward | | `IgnoreExceptionsTest` | A resource that throws for its first 300ms, using `ignoreExceptionsInstanceOf(...)` to treat that as "not yet", not a failure | Two more transcripts in `docs/output/` are real failures from tests that are *not* in the suite above: `01-sleep-guesses-wrong.txt` (a fixed `Thread.sleep(100)` against a 220ms operation) and `08-exception-propagates-immediately.txt` (the same resource as `IgnoreExceptionsTest`, minus `ignoreExceptionsInstanceOf(...)`). Both were real `mvn test` runs, captured once, then the failing test class was deleted — the mistake is preserved as a transcript, not as a permanently red test. ## The pom.xml trap this module exists to document The first version of this module's `pom.xml` depended on `org.springframework.kafka:spring-kafka` directly, the way every pre-Boot-4.1 tutorial does. It compiled. Every `@SpringBootTest` using Kafka then failed with an empty `ConcurrentLinkedQueue` and no error at context startup, because in Boot 4.1.1 the Kafka autoconfiguration classes (`KafkaTemplate`, `ConsumerFactory`, the `KafkaListenerEndpointRegistry` that `@KafkaListener` needs) moved out of the monolithic `spring-boot-autoconfigure` jar into their own module, `org.springframework.boot:spring-boot-kafka` — pulled in by the new `org.springframework.boot:spring-boot-starter-kafka`, not by `spring-kafka` alone. See `pom.xml`'s own comments and the post for the full diagnosis. ## Versions Read from `spring-boot-dependencies-4.1.1.pom` and `spring-boot-starter-test-4.1.1.pom` on Maven Central, not from release notes: **JDK 25** (Temurin 25.0.4.1+1), **Spring Boot 4.1.1**, **Spring Kafka 4.1.1**, **Awaitility 4.3.0** (already on the classpath via `spring-boot-starter-test` — no explicit `` for it anywhere in `pom.xml`). ## Quickstart ``` mvn test ``` 11 tests, 0 failures. `KafkaListenerAwaitTest` starts a real in-process Kafka broker (`@EmbeddedKafka`) and takes a few seconds; `DefaultTimingTest` deliberately waits out Awaitility's real 10-second default timeout once, so the suite as a whole takes about 30 seconds. ## Captured output | File | What it's from | |---|---| | `00-full-test-run.txt` | The full `mvn test` run, 11/11 green | | `01-sleep-guesses-wrong.txt` | Real failure: a guessed `Thread.sleep(100)` against a 220ms operation (test since removed) | | `02-await-finds-it.txt` | The fixed version: `await().untilAsserted(...)` against the same operation | | `03-default-timeout-is-ten-seconds.txt` | `await().until(() -> false)` timing out at ~10,000ms with no override | | `04-default-poll-interval-is-100ms.txt` | Real poll timestamps, ~100ms apart, with no override | | `05-kafka-listener-await.txt` | `await()` for an `@KafkaListener` to consume a record just sent | | `06-scheduled-job-await.txt` | `await()` for 3 more executions of an already-running `@Scheduled` job | | `07-uncaught-exception-handler-swap.txt` | Proof that `await()` swaps and restores the JVM's default uncaught-exception handler | | `08-exception-propagates-immediately.txt` | Real failure: an exception from the polled condition, with no `ignoreExceptionsInstanceOf(...)`, failing on the first poll (test since removed) | | `09-ignore-exceptions-waits-it-out.txt` | The fixed version: the same resource, with `ignoreExceptionsInstanceOf(...)` | | `10-missing-kafka-starter-failure.txt` | Real failure: the Kafka test with `spring-kafka` as a direct dependency instead of `spring-boot-starter-kafka` (pom.xml since fixed) | | `11-pom-diagnosis.txt` | The real `curl`/`grep` transcript that found the Boot 4.1 Kafka module split against Maven Central's own POMs | ## What's sourced from documentation, not run here Awaitility's own `@since` Javadoc tags, Spring Boot's own migration notes for the Kafka module split, and the general shape of `@KafkaListener`/`@EmbeddedKafka` wiring are cited from the real jars and POMs on Maven Central (see the post for exact artifact coordinates), not re-derived from scratch in this module. ## Licence MIT — see [LICENSE](../LICENSE).