Awaitility for Java: The Complete Guide with Examples
1. What Is Awaitility and Why You Need It
Awaitility is a powerful Java library that provides an expressive DSL (Domain-Specific Language) for writing asynchronous tests. In modern development, we constantly deal with async operations: HTTP requests, database calls, message queues, CompletableFuture, reactive streams, and more. Traditional testing approaches like Thread.sleep() produce brittle, slow, and unreliable tests. Awaitility solves this by letting you declaratively define expectations: "wait, but no longer than 10 seconds, until a condition becomes true, checking every 100 milliseconds."
The library's core value is making async tests stable, readable, and performant. Instead of guessing how long an operation will take, you describe the system's final state. Awaitility handles wait times, polling intervals, and exception handling automatically. This is especially critical when testing microservices, integration scenarios, and distributed systems where execution times can vary widely.
Awaitility integrates with all major testing frameworks (JUnit 4/5, TestNG, Spock) and works in both unit and integration tests. It doesn't impose any specific async approach — it works with callbacks, Futures, reactive streams, Event Bus, and more. The only requirement is the ability to check system state at a given point in time.
2. Installation
Awaitility is distributed through Maven Central. Add it to your project using your preferred dependency manager.
Maven (pom.xml)
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.1</version>
<scope>test</scope>
</dependency>
Gradle (build.gradle)
testImplementation 'org.awaitility:awaitility:4.2.1'
Gradle Kotlin DSL (build.gradle.kts)
testImplementation("org.awaitility:awaitility:4.2.1")
Check the Latest Version
You can find the latest version on MVNRepository. As of this writing, the latest stable release is 4.2.1 (compatible with Java 8+).
3. Quick Start — Minimal Working Example
Let's look at a simple example: we have a service that updates a value asynchronously. We want to wait until the value equals 5.
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class QuickStartTest {
@Test
public void shouldWaitForAsyncUpdate() {
// Simulate an async service
AtomicInteger counter = new AtomicInteger(0);
// Start a background thread that increments the counter
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
counter.incrementAndGet();
}
}).start();
// Awaitility waits until counter equals 5
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.until(() -> counter.get() == 5);
// Verify the result
assert counter.get() == 5 : "Counter should be 5";
}
}
This test will complete reliably — whether the async operation takes 1 second or 4 seconds — as long as it finishes within the 5-second timeout.