AssertJ Getting Started: Fluent Assertions for Java
AssertJ is a Java assertion library that replaces the default assertions in JUnit and TestNG with a fluent, chainable API. Instead of assertEquals(expected, actual) you write assertThat(actual).isEqualTo(expected) — readable left to right, with IDE auto-complete guiding you to the right assertion for each type.
Why AssertJ
Three reasons teams switch from JUnit assertions:
1. Readable error messages. When an assertion fails, AssertJ tells you what you got versus what you expected, with the full context of what was being checked. JUnit 4's assertEquals reverses the expected/actual order in error messages depending on which argument you passed first.
2. Auto-complete by type. assertThat(myList) returns a ListAssert with list-specific methods. assertThat(myString) returns a StringAssert. Your IDE shows you the available assertions for the type you're asserting on.
3. Chaining. Multiple assertions on the same object without repeating assertThat(...).
Setup
Maven
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>Gradle
testImplementation 'org.assertj:assertj-core:3.25.3'Static import in your test class:
import static org.assertj.core.api.Assertions.*;Or import specific methods:
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatNoException;Basic Assertions
// Strings
assertThat("Hello World")
.isNotNull()
.isNotEmpty()
.startsWith("Hello")
.endsWith("World")
.contains("lo Wo")
.hasSize(11);
// Numbers
assertThat(42)
.isGreaterThan(0)
.isLessThanOrEqualTo(100)
.isBetween(10, 50)
.isEven();
assertThat(3.14)
.isCloseTo(Math.PI, within(0.01));
// Booleans
assertThat(true).isTrue();
assertThat(false).isFalse();
// Null checks
assertThat(result).isNotNull();
assertThat(optional).isNull();
// Same instance
assertThat(actual).isSameAs(expected);
assertThat(actual).isNotSameAs(other);Object Assertions
// Equality
assertThat(user).isEqualTo(expectedUser); // uses .equals()
assertThat(user).isNotEqualTo(otherUser);
// Field-by-field comparison (ignores .equals())
assertThat(user)
.usingRecursiveComparison()
.isEqualTo(expectedUser);
// Ignore specific fields (e.g. generated IDs, timestamps)
assertThat(user)
.usingRecursiveComparison()
.ignoringFields("id", "createdAt")
.isEqualTo(expectedUser);
// Check specific fields
assertThat(user)
.extracting("name", "email")
.containsExactly("John Doe", "john@example.com");Collection Assertions
List<String> names = List.of("Alice", "Bob", "Charlie");
assertThat(names)
.isNotEmpty()
.hasSize(3)
.contains("Alice", "Bob") // contains these (any order)
.containsExactly("Alice", "Bob", "Charlie") // exact order
.containsExactlyInAnyOrder("Charlie", "Alice", "Bob")
.doesNotContain("Dave")
.startsWith("Alice")
.endsWith("Charlie");
// Filter and assert
assertThat(names)
.filteredOn(name -> name.length() > 4)
.containsExactlyInAnyOrder("Alice", "Charlie");
// Extract field from collection of objects
List<User> users = List.of(new User("Alice", 30), new User("Bob", 25));
assertThat(users)
.extracting(User::getName)
.containsExactlyInAnyOrder("Alice", "Bob");
assertThat(users)
.extracting("name", "age")
.containsExactlyInAnyOrder(
tuple("Alice", 30),
tuple("Bob", 25)
);Exception Assertions
// Assert exception is thrown
assertThatThrownBy(() -> userService.findById(999L))
.isInstanceOf(UserNotFoundException.class)
.hasMessage("User not found: 999")
.hasMessageContaining("999");
// With cause
assertThatThrownBy(() -> service.connect())
.isInstanceOf(ServiceException.class)
.hasCauseInstanceOf(IOException.class);
// Assert no exception
assertThatNoException().isThrownBy(() -> service.healthCheck());
// Specific exception type
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new User(null, -1))
.withMessage("Name cannot be null");Optional Assertions
Optional<User> user = userService.findByEmail("john@example.com");
assertThat(user)
.isPresent()
.hasValueSatisfying(u -> {
assertThat(u.getName()).isEqualTo("John");
assertThat(u.getAge()).isGreaterThan(0);
});
assertThat(userService.findByEmail("nobody@example.com"))
.isEmpty();Map Assertions
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 87);
assertThat(scores)
.hasSize(2)
.containsKey("Alice")
.containsValue(95)
.containsEntry("Bob", 87)
.doesNotContainKey("Charlie");Chaining with satisfies
For complex object assertions without custom matchers:
assertThat(response)
.satisfies(r -> {
assertThat(r.getStatus()).isEqualTo(200);
assertThat(r.getBody()).isNotEmpty();
assertThat(r.getHeaders()).containsKey("Content-Type");
});JUnit 5 Integration
AssertJ works with JUnit 5 out of the box. For soft assertions (collect all failures):
import org.assertj.core.api.SoftAssertions;
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(SoftAssertionsExtension.class)
class UserServiceTest {
@Test
void createUser(SoftAssertions softly) {
User user = userService.create("John", "john@example.com");
softly.assertThat(user.getId()).isNotNull();
softly.assertThat(user.getName()).isEqualTo("John");
softly.assertThat(user.getEmail()).isEqualTo("john@example.com");
softly.assertThat(user.getCreatedAt()).isNotNull();
// All failures reported, not just the first
}
}AssertJ's API covers virtually every assertion scenario. The combination of auto-complete, readable failure messages, and method chaining makes it the standard choice for Java projects that care about test quality.