A Java DSL for synchronizing asynchronous operations
—
Utilities for waiting on private field values and object state changes using reflection, ideal for testing internal component state.
Create field supplier builders for accessing object fields via reflection.
/**
* Create field supplier builder for instance fields
* @param object instance containing the field to monitor
* @return FieldSupplierBuilder for field specification
*/
static FieldSupplierBuilder fieldIn(Object object);/**
* Create field supplier builder for static fields
* @param clazz class containing the static field to monitor
* @return FieldSupplierBuilder for field specification
*/
static FieldSupplierBuilder fieldIn(Class<?> clazz);Builder interface for specifying field selection criteria and creating callable suppliers.
/**
* Specify expected field type for selection and create a supplier
* @param fieldType class of the field type (generic parameter preserved)
* @return NameAndAnnotationFieldSupplier for additional criteria or direct usage
*/
<T> NameAndAnnotationFieldSupplier<T> ofType(Class<T> fieldType);The field access API provides nested supplier classes that implement Callable<T> for different selection criteria combinations.
Primary supplier class returned by ofType() that supports additional field narrowing criteria.
interface NameAndAnnotationFieldSupplier<T> extends Callable<T> {
/**
* Narrow field selection by field name
* @param fieldName the exact name of the field
* @return AnnotationFieldSupplier for optional annotation criteria
*/
AnnotationFieldSupplier<T> andWithName(String fieldName);
/**
* Narrow field selection by annotation type
* @param annotationType the annotation class that must be present on the field
* @return NameFieldSupplier for optional name criteria
*/
NameFieldSupplier<T> andAnnotatedWith(Class<? extends Annotation> annotationType);
/**
* Use the field supplier directly (finds first field of specified type)
* @return the field value
*/
T call() throws Exception;
}Supplier for fields selected by type and name, with optional annotation criteria.
interface AnnotationFieldSupplier<T> extends Callable<T> {
/**
* Further narrow field selection by annotation (requires exact match of name, type, and annotation)
* @param annotationType the annotation class that must be present on the field
* @return Callable supplier for the precisely specified field
*/
Callable<T> andAnnotatedWith(Class<? extends Annotation> annotationType);
/**
* Use the field supplier (finds field by name and type)
* @return the field value
*/
T call();
}Supplier for fields selected by type and annotation, with optional name criteria.
interface NameFieldSupplier<T> extends Callable<T> {
/**
* Further narrow field selection by field name (requires exact match of annotation, type, and name)
* @param fieldName the exact name of the field
* @return Callable supplier for the precisely specified field
*/
Callable<T> andWithName(String fieldName);
/**
* Use the field supplier (finds field by annotation and type)
* @return the field value
*/
T call() throws Exception;
}
#### NameAndAnnotationFieldSupplier
Primary supplier class that allows chaining name and annotation criteria.
```java { .api }
/**
* Add name constraint to field selection
* @param fieldName exact name of the field
* @return AnnotationFieldSupplier for optional annotation constraint
*/
AnnotationFieldSupplier<T> andWithName(String fieldName);
/**
* Add annotation constraint to field selection
* @param annotationType annotation class that must be present on field
* @return NameFieldSupplier for optional name constraint
*/
NameFieldSupplier<T> andAnnotatedWith(Class<? extends Annotation> annotationType);
/**
* Get field value using type constraint only
* @return field value cast to expected type
* @throws FieldNotFoundException if no field of specified type found
*/
T call() throws Exception;Supplier for fields selected by type and name, allowing annotation constraint.
/**
* Add annotation constraint (returns self for chaining)
* @param annotationType annotation class that must be present on field
* @return this supplier for call() execution
*/
Callable<T> andAnnotatedWith(Class<? extends Annotation> annotationType);
/**
* Get field value using type and name constraints
* @return field value cast to expected type
* @throws FieldNotFoundException if field not found or type mismatch
*/
T call() throws Exception;Supplier for fields selected by type and annotation, allowing name constraint.
/**
* Add name constraint (returns anonymous Callable)
* @param fieldName exact name of the field
* @return Callable for call() execution
*/
Callable<T> andWithName(String fieldName);
/**
* Get field value using type and annotation constraints
* @return field value cast to expected type
* @throws FieldNotFoundException if field not found or type mismatch
*/
T call() throws Exception;The field access API supports several selection patterns through method chaining:
// Access field by type only (first field of matching type)
await().until(fieldIn(object).ofType(int.class), equalTo(42));// Access field by type and name
await().until(fieldIn(object).ofType(String.class).andWithName("username"), equalTo("admin"));// Access field by type and annotation
await().until(fieldIn(object).ofType(boolean.class).andAnnotatedWith(MyAnnotation.class), equalTo(true));// Access field by type, name, and annotation (most specific)
await().until(fieldIn(object).ofType(int.class)
.andWithName("counter")
.andAnnotatedWith(MyAnnotation.class), greaterThan(10));// Alternative order: type, annotation, then name
await().until(fieldIn(object).ofType(int.class)
.andAnnotatedWith(MyAnnotation.class)
.andWithName("counter"), greaterThan(10));// Access static fields using class reference
await().until(fieldIn(DatabasePool.class).ofType(int.class).andWithName("activeConnections"), lessThan(10));
// Access static field by type only
await().until(fieldIn(ConfigManager.class).ofType(boolean.class), equalTo(true));Field access can throw several exceptions during reflection operations:
/**
* Thrown when field cannot be found based on specified criteria
*/
class FieldNotFoundException extends RuntimeException {
public FieldNotFoundException(String message);
}
/**
* Thrown when multiple fields match criteria and disambiguation is required
*/
class TooManyFieldsFoundException extends RuntimeException {
public TooManyFieldsFoundException(String message);
}try {
await().until(fieldIn(object).ofType(String.class), equalTo("expected"));
} catch (ConditionTimeoutException e) {
// Timeout occurred before field reached expected value
if (e.getCause() instanceof FieldNotFoundException) {
// Field of specified type doesn't exist
}
}// Wait for nested object field value
await().until(fieldIn(userService).ofType(DatabaseConnection.class)
.andWithName("connection"),
connection -> connection.isActive());
// Wait for collection field size
await().until(fieldIn(cache).ofType(ConcurrentMap.class)
.andWithName("entries"),
map -> ((Map<?, ?>) map).size() > 100);// Wait for field annotated with @Metric
await().until(fieldIn(processor).ofType(long.class)
.andAnnotatedWith(Metric.class),
greaterThan(1000L));
// Wait for @ConfigValue annotated field to change
await().until(fieldIn(configService).ofType(String.class)
.andAnnotatedWith(ConfigValue.class)
.andWithName("databaseUrl"),
not(equalTo("localhost:3306")));// Type-safe field access preserves generics
AtomicReference<String> result = await().until(
fieldIn(asyncService).ofType(AtomicReference.class)
.andWithName("result"),
ref -> ref.get() != null);
// Works with complex generic types
List<Order> orders = await().until(
fieldIn(orderService).ofType(List.class)
.andWithName("pendingOrders"),
list -> !((List<?>) list).isEmpty());Install with Tessl CLI
npx tessl i tessl/maven-org-awaitility--awaitility