Contents

AssertJ Core is included in spring-boot-starter-test. For standalone projects:

<dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.26.0</version> <scope>test</scope> </dependency>

All assertions start with a static import: import static org.assertj.core.api.Assertions.*;

The class below shows the implementation. Key points are highlighted in the inline comments.

import static org.assertj.core.api.Assertions.*; // Equality assertThat(42).isEqualTo(42); assertThat("hello").isNotEqualTo("world"); // Null checks assertThat(user).isNotNull(); assertThat(result).isNull(); // Boolean assertThat(list.isEmpty()).isTrue(); assertThat(service.isActive()).isFalse(); // Type check assertThat(obj).isInstanceOf(String.class); // Comparable assertThat(score).isGreaterThan(0) .isLessThanOrEqualTo(100) .isBetween(1, 99); // Same reference assertThat(actual).isSameAs(expected);

The class below shows the implementation. Key points are highlighted in the inline comments.

String email = "alice@example.com"; assertThat(email) .isNotEmpty() .isNotBlank() .startsWith("alice") .endsWith(".com") .contains("@") .doesNotContain("bob") .matches("^[\\w.]+@[\\w.]+\\.[a-z]{2,}$") .hasSize(17) .isEqualToIgnoringCase("ALICE@EXAMPLE.COM");

The class below shows the implementation. Key points are highlighted in the inline comments.

List<String> fruits = List.of("apple", "banana", "cherry"); assertThat(fruits) .hasSize(3) .isNotEmpty() .contains("banana") .containsExactly("apple", "banana", "cherry") // exact order .containsExactlyInAnyOrder("cherry", "apple", "banana") .doesNotContain("durian") .allMatch(f -> f.length() > 3) .anyMatch(f -> f.startsWith("a")) .noneMatch(f -> f.isBlank()); // Extracting fields from a list of objects List<User> users = List.of( new User(1L, "Alice", "admin"), new User(2L, "Bob", "user") ); assertThat(users) .extracting(User::name) .containsExactlyInAnyOrder("Alice", "Bob"); assertThat(users) .extracting("name", "role") .containsExactly( tuple("Alice", "admin"), tuple("Bob", "user") ); // Map assertions Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 80); assertThat(scores) .containsKey("Alice") .containsEntry("Bob", 80) .doesNotContainKey("Charlie") .hasSize(2);

Use usingRecursiveComparison() for deep field-by-field equality without requiring equals(), and extracting() to pluck specific fields for assertion.

Order expected = new Order("o1", "Widget", 3, OrderStatus.PENDING); Order actual = orderService.place("Widget", 3); // Deep recursive comparison — ignores equals() assertThat(actual) .usingRecursiveComparison() .ignoringFields("id", "createdAt") // skip generated fields .isEqualTo(expected); // Extracting specific fields assertThat(actual) .extracting(Order::product, Order::quantity) .containsExactly("Widget", 3); // Conditions assertThat(actual.status()).satisfies(status -> { assertThat(status).isNotNull(); assertThat(status.name()).isIn("PENDING", "PROCESSING"); });

The class below shows the implementation. Key points are highlighted in the inline comments.

// Assert type and message assertThatThrownBy(() -> userService.findById(-1L)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("must be positive") .hasMessageStartingWith("User ID"); // Assert a specific exception type with typed result assertThatExceptionOfType(EntityNotFoundException.class) .isThrownBy(() -> userService.findById(999L)) .withMessage("User 999 not found") .withNoCause(); // Assert no exception is thrown assertThatNoException() .isThrownBy(() -> userService.findById(1L)); // assertThatCode is another common form assertThatCode(() -> configService.reload()) .doesNotThrowAnyException();

Normal assertions stop at the first failure. SoftAssertions collects all failures and reports them together — useful when you need to validate a full object in one test pass.

import org.assertj.core.api.SoftAssertions; @Test void validatesUserProfile() { User user = userService.loadProfile(1L); SoftAssertions softly = new SoftAssertions(); softly.assertThat(user.name()).isEqualTo("Alice"); softly.assertThat(user.email()).contains("@"); softly.assertThat(user.age()).isBetween(18, 120); softly.assertThat(user.role()).isIn("admin", "user", "viewer"); softly.assertAll(); // throws AssertionError listing ALL failures } // Alternatively use the lambda form which calls assertAll automatically @Test void validatesOrderSoftly() { Order order = orderService.place("Widget", 5); SoftAssertions.assertSoftly(softly -> { softly.assertThat(order.id()).isNotNull(); softly.assertThat(order.product()).isEqualTo("Widget"); softly.assertThat(order.quantity()).isEqualTo(5); softly.assertThat(order.status()).isEqualTo(OrderStatus.PENDING); }); }

Extend AbstractAssert to create domain-specific assertion classes that read naturally and produce clear failure messages.

import org.assertj.core.api.AbstractAssert; public class OrderAssert extends AbstractAssert<OrderAssert, Order> { public OrderAssert(Order order) { super(order, OrderAssert.class); } public static OrderAssert assertThat(Order order) { return new OrderAssert(order); } public OrderAssert hasProduct(String product) { isNotNull(); if (!actual.product().equals(product)) { failWithMessage("Expected order product to be <%s> but was <%s>", product, actual.product()); } return this; } public OrderAssert isPending() { isNotNull(); if (actual.status() != OrderStatus.PENDING) { failWithMessage("Expected order to be PENDING but was <%s>", actual.status()); } return this; } public OrderAssert hasQuantityGreaterThan(int min) { isNotNull(); if (actual.quantity() <= min) { failWithMessage("Expected quantity > %d but was %d", min, actual.quantity()); } return this; } } // Usage — reads like plain English Order order = orderService.place("Gadget", 3); OrderAssert.assertThat(order) .hasProduct("Gadget") .isPending() .hasQuantityGreaterThan(0);