Close

AssertJ - Recursive Comparison

[Last Updated: Mar 3, 2026]

Testing complex objects often requires comparing all fields deeply — including nested objects. If the class does not override equals(), a simple isEqualTo() will only compare references. AssertJ's usingRecursiveComparison() performs a deep field-by-field comparison without requiring equals() to be defined.

Why This Matters

In many projects, domain objects (especially DTOs or entities) don't define equals(). Rather than polluting the domain model with test-only equality logic, usingRecursiveComparison() keeps that concern in the test layer.

Ignoring Fields

You can exclude specific fields from comparison — for example, auto-generated IDs or timestamps — using ignoringFields("id", "createdAt") or ignoringFieldsMatchingRegexes(".*Id").

Example

package com.logicbig.example;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

public class RecursiveComparisonExample {

    static class Address {
        String city;
        String zip;

        Address(String city,
                String zip) {
            this.city = city;
            this.zip = zip;
        }
    }

    static class Customer {
        int id;
        String name;
        Address address;

        Customer(int id,
                 String name,
                 Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    }

    @Test
    void myTest() {
        Customer actual = new Customer(1,
                                       "Alice",
                                       new Address("London", "SW1A"));
        Customer expected = new Customer(99,
                                         "Alice",
                                         new Address("London", "SW1A"));

        // ignore id (differs), compare everything else deeply
        assertThat(actual).usingRecursiveComparison()
                          .ignoringFields("id")
                          .isEqualTo(expected);
        System.out.println("Recursive comparison with ignored field passed");

        // full comparison - same id
        Customer same = new Customer(1, "Alice", new Address("London", "SW1A"));
        assertThat(actual).usingRecursiveComparison().isEqualTo(same);
        System.out.println("Full recursive comparison passed");
    }
}

Output

$ mvn clean test -Dtest=*
[INFO] Scanning for projects...
[INFO]
[INFO] ---------< com.logicbig.example:assertj-recursive-comparison >----------
[INFO] Building assertj-recursive-comparison 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ assertj-recursive-comparison ---
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ assertj-recursive-comparison ---
[INFO] skip non existing resourceDirectory D:\example-projects\assertj\assertj-recursive-comparison\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ assertj-recursive-comparison ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ assertj-recursive-comparison ---
[INFO] skip non existing resourceDirectory D:\example-projects\assertj\assertj-recursive-comparison\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ assertj-recursive-comparison ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 1 source file with javac [debug target 17] to target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ assertj-recursive-comparison ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.RecursiveComparisonExample
Recursive comparison with ignored field passed
Full recursive comparison passed
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.173 s -- in com.logicbig.example.RecursiveComparisonExample
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.852 s
[INFO] Finished at: 2026-03-03T14:22:38+08:00
[INFO] ------------------------------------------------------------------------

Conclusion

The output confirms that recursive comparison correctly evaluates field equality deeply, including nested Address fields. The ignored field id is excluded from the comparison, simulating the common scenario where generated identifiers differ between the actual result and the expected template.

Example Project

Dependencies and Technologies Used:

  • assertj-core 3.27.7 (Rich and fluent assertions for testing in Java)
  • junit-jupiter-engine 6.0.2 (Module "junit-jupiter-engine" of JUnit)
  • JDK 17
  • Maven 3.9.11

AssertJ - Recursive Comparison Select All Download
  • assertj-recursive-comparison
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • RecursiveComparisonExample.java

    See Also

    Join