Testing asynchronous operations in Java can be challenging, especially when working with microservices, reactive applications, and event-driven architectures where you need to wait for conditions to become true without using unreliable Thread.sleep() calls. Awaitility is a lightweight Java library that simplifies waiting in your tests, allowing you to write clear, fluent, and robust asynchronous tests. In this updated guide, you will learn how to use Awaitility for testing Java, Spring Boot, Quarkus, and Jakarta EE applications, with practical examples aligned with modern testing practices for cloud-native environments and CI/CD pipelines.
Waiting in Java properly
In order to make a pause during the execution of Java code you can use two main approaches:
Java’s Thread.sleep() method is a built-in method that allows a thread to pause execution for a specified amount of time. It is simple to use and can be integrated easily into any Java code. However, it is not very flexible as it can only pause execution for a fixed amount of time, and it does not provide any way to check if a certain condition has been met before continuing execution.
Awaitility is a third-party library that provides a more flexible and powerful way to handle thread pauses and waiting for conditions in Java. It provides a variety of methods for waiting for specific conditions, such as waiting for a method to return a certain value or for a specific exception to be thrown. Additionally, Awaitility allows for more fine-grained control over the waiting process, such as setting a timeout for the wait and polling the condition at a specific interval.
Here are the advantages and disadvantages of each solution:
Pros of Thread.sleep():
- Simple to use
- Built-in to Java
Cons of Thread.sleep():
- Only allows for pausing execution for a fixed amount of time
- No way to check if a certain condition has been met before continuing execution
Pros of Awaitility:
- More flexible and powerful way to handle thread pauses and waiting for conditions
- Provides various methods for waiting for specific conditions
- Allows for more fine-grained control over the waiting process
Cons of Awaitility:
- Third-party library, not built-in to Java
Testing applications with Awaitility
Awaitility integrates seamlessly with JUnit 5, Testcontainers, and Spring Boot Test, making it a valuable tool in your testing toolbox when verifying async operations such as message consumption from Kafka, REST API readiness, or database state changes. For example, you can combine Testcontainers and Awaitility to wait for your PostgreSQL or Kafka containers to become ready before running your integration tests, eliminating race conditions in your CI pipelines.
For example, here is a basic sample:
await().atMost(5, SECONDS).until(checkCondition());
In the above example, checkCondition contains the condition to verify. Therefore, we will wait at most 5 seconds to verify that condition is true. If you don’t specify any timeout Awaitility will wait for 10 seconds and then throw a ConditionTimeoutException if the condition has not been fulfilled.
Even in this simple form, Awaitility has some advantages over Thread.sleep(). Indeed we don’t have to wait for a fixed amount of time if the condition is already verified.
Let’s see a slightly more complex example. In this example, we are introducing untilAsserted which waits until a Runnable supplier execution passes. In this example, we are asserting that in, at most 3 seconds, the server log file will contain a text in it:
await().atMost(3, TimeUnit.SECONDS).untilAsserted(() -> {
String content =null;
try {
Path filePath = Path.of("/home/jboss/wildfly-26.1.0.Final/standalone/log/server.log");
content = Files.readString(filePath);
} catch (IOException exc) { exc.printStackTrace(); }
Assertions.assertTrue(content.indexOf("Admin console listening") > -1);
});
If your tests include several checks from , it is a good idea to introduce an alias for it. This way you will be able to catch easily which test is failing:
await("Check my Heros").atMost(3, SECONDS).until(herosAvailable());
Checking Types
Next, let’s see how to verify an assertion which requires checking a time limit and the returned Type, including its size.
Here is an Endpoint which produces an array of Hero objects:
@GET
public Hero[] get() {
Hero hero[] = new Hero[3];
hero[0] = new Hero("Spiderman");
hero[1] = new Hero("Batman");
hero[2] = new Hero("Hulk");
return hero;
}
The following condition will check that, within 2 seconds, the HTTP GET endpoint returns an array of Hero with a lenght of 3:
await().atMost(2, SECONDS)
.untilAsserted(() -> Assertions.assertEquals(3, get().as(Hero[].class).length));
Using Poll intervals
A poll interval tells Awaitility how often it should evaluate the condition which is in the until method. In the following example, we use a poll interval of 250 milliseconds with an initial delay of 50 milliseconds until
with().pollInterval(250, TimeUnit.MILLISECONDS).and().with().pollDelay(50, MILLISECONDS).await().until(herosAvailable());
Check the condition between a time frame
You can also check that the await condition is true only between a specific timing. For this purpose, you can use atLeast in your condition. For example:
await().atLeast(50, TimeUnit.MILLISECONDS).and().atMost(3, SECONDS).until(herosAvailable());
In the above example, the condition will evaluate to true if the method herosAvailable takes between 50 ms and 3 seconds. If it evaluates to true before, you will see the following Exception:
Condition was evaluated in 10 milliseconds which is earlier than expected minimum timeout 50 milliseconds
Check condition during an amount of time
Finally, it’s possible to assert that a condition evaluates to true during a time frame. For example, if we were to check that the endpoint returns the same number of Hero during 800 to 1500 ms:
await().during(800, MILLISECONDS).atMost(1500, MILLISECONDS).untilAsserted(() -> Assertions.assertEquals(3, get().as(Hero[].class).length));
In the above example, there won’t be any Exception if the condition evaluates to true earlier than the time frame.
How to build projects using Awaitility
Finally, in order to build Test classes using Awaitility you need to include the following dependency in your pom.xml (updated June 2025) :
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.3.0</version>
<scope>test</scope>
</dependency>
🌱 Alternative Approaches to Consider
- Project Reactor’s StepVerifier: If you are using reactive programming with Reactor,
StepVerifiercan be an alternative to Awaitility for testing reactive streams deterministically. - AssertJ Awaitility: Awaitility’s integration with AssertJ provides a fluent API for assertions while waiting, making your tests expressive and easier to maintain.
- Spring Boot’s
@SpringBootTestwith@DynamicPropertySource: For readiness checks without Awaitility, you can leverage Spring Boot’s test lifecycle hooks for certain scenarios, although Awaitility remains superior for custom conditions.
Conclusion
Awaitility is a powerful and lightweight tool that makes testing asynchronous operations in Java, Spring Boot, Quarkus, and Jakarta EE applications easy and reliable. By allowing you to wait for specific conditions in a fluent and readable way, Awaitility helps you eliminate flaky tests and replace inefficient Thread.sleep() calls in your test suites. You can find the source code for this example on GitHub: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/awaitility