Member-only story
Killing Flaky @KafkaListener Tests with Testcontainers and Awaitility
If you have ever written an integration test for a @KafkaListener, you know the pattern. You publish a message, add a Thread.sleep(2000), then assert. It passes on your machine. It fails on the CI runner under load. You bump the sleep to 5 seconds. Now every build wastes five seconds even when the message arrived in 40 milliseconds.
Kafka processing is asynchronous. Your test thread and the listener thread run independently, so the moment you call send() the message is not yet consumed. Thread.sleep is a guess about how long that gap is, and a guess is exactly what makes a test flaky. This post replaces the guess with a real broker (Testcontainers) and a polling assertion (Awaitility). Versions used: Spring Boot 3.4, Java 21, org.testcontainers:kafka 1.20, and awaitility 4.2.
The listener under test
Nothing exotic. An order-created event comes in, we persist a projection.
@Component
@RequiredArgsConstructor
@Slf4j
public class OrderEventListener {
private final OrderProjectionRepository repository;
@KafkaListener(topics = "orders.created", groupId = "order-projection")
public void onOrderCreated(OrderCreatedEvent event) {
log.info("Received order {}", event.orderId());
repository.save(new…







