To assert that two lists are equal ignoring order, we use AssertJ’s assertThat(actual).containsExactlyInAnyOrderElementsOf(expected), which passes only when both lists hold the same elements the same number of times, in any order. Hamcrest’s containsInAnyOrder() and Commons Collections’ CollectionUtils.isEqualCollection() check the same thing, and with JUnit alone we compare sorted copies of both lists.
We need an order-free assertion whenever the code under test does not promise an order, for example a query without ORDER BY, a result built from a HashSet or tasks finished by parallel threads. A plain assertEquals() on such lists fails at random, which is the classic flaky test.
The following example asserts the roles a service returns for a user in three ways.
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
List<String> actual = List.of("viewer", "admin", "editor");
List<String> expected = List.of("admin", "editor", "viewer");
assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); // passes
assertEquals(expected.stream().sorted().toList(), actual.stream().sorted().toList()); // passes
assertEquals(expected, actual); // AssertionFailedError: order differs
Notice that the plain assertEquals() on the last line fails although both lists hold the same roles, because List.equals() compares positions. We cover Commons Collections, Hamcrest and AssertJ in turn, compare how each one treats duplicates, and finish with JUnit-only options and lists of objects.
1. Using Commons Collections4’s CollectionUtils.isEqualCollection()
The CollectionUtils class of Apache Commons Collections is a good fit when the comparison is part of production code, not only of a test. To use it, we add the commons-collections4 dependency. The examples use version 4.5.0.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.5.0</version>
</dependency>
The isEqualCollection() method returns true if both lists contain the same elements with the same cardinalities, i.e. each element appears the same number of times in both lists.
import org.apache.commons.collections4.CollectionUtils;
List<String> roles = List.of("admin", "editor", "viewer");
boolean same = CollectionUtils.isEqualCollection(roles, List.of("viewer", "admin", "editor")); // true
boolean other = CollectionUtils.isEqualCollection(roles, List.of("guest", "admin", "editor")); // false
The cardinality check makes the method safe with duplicates. Two lists with the same values but different counts are not equal.
boolean counts = CollectionUtils.isEqualCollection(List.of("a", "a", "b"), List.of("a", "b", "b")); // false
boolean repeated = CollectionUtils.isEqualCollection(List.of("a", "b", "b"), List.of("b", "a", "b")); // true
By default, the elements are compared with equals(). Sometimes we receive lists from different sources, and the elements do not follow the same equality rules, such as role names in different letter cases. For that case, the method accepts an Equator, which defines both the equality and the hash used for counting.
import org.apache.commons.collections4.Equator;
class CaseInsensitiveEquator implements Equator<String> {
@Override
public boolean equate(String first, String second) {
return first.equalsIgnoreCase(second);
}
@Override
public int hash(String value) {
return value.toLowerCase(Locale.ROOT).hashCode();
}
}
boolean ignoreCase = CollectionUtils.isEqualCollection(
List.of("admin", "viewer"), List.of("VIEWER", "Admin"), new CaseInsensitiveEquator()); // true
The cardinalities are counted in a hash map that uses the hash() result as the hash code of each element, so equal elements must produce the same hash. Here, both “Admin” and “admin” become the lowercase key “admin”.
2. Using Hamcrest’s Matchers.containsInAnyOrder()
Hamcrest is a matcher library that many projects already have through Spring Boot’s test starter. The old hamcrest-all artifact stopped at version 1.3, and the current artifact is org.hamcrest:hamcrest, version 3.0.
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
The containsInAnyOrder() matcher matches when each expected item matches its own element of the actual list, in any order. For a positive match, both lists must have the same size, so we do not compare the sizes ourselves.
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalToIgnoringCase;
List<String> actual = List.of("viewer", "admin", "editor");
List<String> expected = List.of("admin", "editor", "viewer");
assertThat(actual, containsInAnyOrder(expected.toArray()));
assertThat(List.of("a", "a", "b"), containsInAnyOrder("a", "b", "b")); // AssertionError
The two-argument assertThat() comes from org.hamcrest.MatcherAssert, not from JUnit’s Assertions, which has no such method in JUnit 5 or 6. For custom equality, we pass one matcher per expected element instead of the values.
List<String> roles = List.of("ADMIN", "Viewer");
assertThat(roles, containsInAnyOrder(equalToIgnoringCase("viewer"), equalToIgnoringCase("admin")));
3. Using AssertJ’s containsExactlyInAnyOrderElementsOf() API
AssertJ gives the most readable failure messages, so it is our first choice in tests. We add the assertj-core dependency, version 3.27.7.
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.7</version>
<scope>test</scope>
</dependency>
The containsExactlyInAnyOrderElementsOf() assertion verifies that the actual list contains all elements of the expected list, each one as many times as in the expected list, in any order. The variant containsExactlyInAnyOrder() takes the expected values as varargs.
List<String> actual = List.of("viewer", "admin", "editor");
assertThat(actual).containsExactlyInAnyOrderElementsOf(List.of("admin", "editor", "viewer"));
assertThat(actual).containsExactlyInAnyOrder("editor", "viewer", "admin");
assertThat(List.of("admin", "viewer")).containsExactlyInAnyOrderElementsOf(List.of("admin", "guest")); // AssertionError
When the assertion fails, AssertJ lists the missing and the unexpected elements separately, which tells us at once what went wrong.
java.lang.AssertionError:
Expecting actual:
["admin", "viewer"]
to contain exactly in any order:
["admin", "guest"]
elements not found:
["guest"]
and elements not expected:
["viewer"]
The similar-looking hasSameElementsAs() ignores duplicates, so it passes for [a, a, b] and [a, b, b]. We use it only when the counts do not matter, and containsExactlyInAnyOrderElementsOf() in every other case.
assertThat(List.of("a", "a", "b")).hasSameElementsAs(List.of("a", "b", "b")); // passes, counts ignored
assertThat(List.of("ADMIN", "Viewer")).usingElementComparator(String.CASE_INSENSITIVE_ORDER)
.containsExactlyInAnyOrderElementsOf(List.of("viewer", "admin"));
The usingElementComparator() call on the last line replaces equals() with a comparator, which is the AssertJ version of the Equator from section 1.
4. Plain Java or JUnit
JUnit 6 has no assertion that ignores order. The methods assertEquals() and assertIterableEquals() both compare positions, so we turn the lists into a form where the order no longer matters and compare that. The examples use JUnit Jupiter 6.0.3.
If the elements are Comparable, we compare sorted copies. On failure, JUnit prints both sorted lists, so the difference is visible at a glance.
List<String> actual = List.of("viewer", "admin", "editor");
List<String> expected = List.of("admin", "editor", "viewer");
assertEquals(expected.stream().sorted().toList(), actual.stream().sorted().toList());
assertEquals(List.of("a", "a", "b").stream().sorted().toList(), List.of("b", "a", "b").stream().sorted().toList()); // AssertionFailedError
If the elements cannot be sorted, we compare frequency maps. A frequency map stores how often each element appears, so it covers both the values and their counts without changing the input lists.
static <T> Map<T, Long> frequencies(List<T> list) {
return list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
List<String> actual = List.of("viewer", "admin", "viewer");
assertEquals(frequencies(List.of("viewer", "viewer", "admin")), frequencies(actual));
Map<String, Long> counts = frequencies(actual); // {viewer=2, admin=1} in any order
An older approach removes each element of the first list from a copy of the second and checks that the copy ends up empty. It works, but it changes a list during the check and costs n * n comparisons, so the frequency map is the better plain Java version. Two shortcuts give wrong results with duplicates and must be avoided.
- Comparing new HashSet<>(first) with new HashSet<>(second) drops the duplicates before the comparison.
- Checking first.containsAll(second) && second.containsAll(first) only asks whether each value appears at least once.
Guava’s HashMultiset and Eclipse Collections’ HashBag are ready-made frequency maps. Their equals() compares elements with counts, so a single assertEquals() does the job in projects that already use one of these libraries.
import com.google.common.collect.HashMultiset;
import org.eclipse.collections.impl.bag.mutable.HashBag;
List<String> actual = List.of("viewer", "admin", "editor");
List<String> expected = List.of("admin", "editor", "viewer");
boolean guava = HashMultiset.create(actual).equals(HashMultiset.create(expected)); // true
boolean eclipse = HashBag.newBag(actual).equals(HashBag.newBag(expected)); // true
5. Which Assertion Treats Duplicates Correctly
The methods look alike but differ in how they count duplicates, and the wrong choice lets a broken result pass. Each row uses the lists [a, a, b] and [a, b, b], which must not be equal.

| Assertion or check | Ignores order | Counts duplicates | Custom equality |
|---|---|---|---|
| AssertJ containsExactlyInAnyOrderElementsOf() | Yes | Yes | usingElementComparator() |
| AssertJ hasSameElementsAs() | Yes | No | usingElementComparator() |
| Hamcrest containsInAnyOrder() | Yes | Yes | One matcher per element |
| Commons isEqualCollection() | Yes | Yes | Equator |
| JUnit assertEquals() on sorted copies or frequency maps | Yes | Yes | Comparator for sorting |
| JUnit assertEquals() / assertIterableEquals() | No | Yes | No |
| HashSet or containsAll() both ways | Yes | No | No |
6. Asserting Lists of Objects Ignoring Order
A real test often checks a list of objects, such as the users a repository returns. Two approaches work well, namely comparing whole objects that define equals(), or extracting one field and comparing the field values.
Say a user service loads the members of a team from the database, and the query has no ORDER BY. The test must not depend on the row order, and the generated IDs differ in every run, so the test compares only the names.
record User(long id, String name) {}
List<User> members = List.of(new User(17, "bob"), new User(5, "ann"));
assertThat(members).extracting(User::name).containsExactlyInAnyOrder("ann", "bob");
List<String> names = members.stream().map(User::name).sorted().toList(); // [ann, bob]
assertEquals(List.of("ann", "bob"), names);
The extracting() call maps each user to its name before the comparison, and the JUnit version does the same with a stream. Because User is a record, comparing whole objects also works when the expected IDs are known.
7. Asserting Lists FAQs
Developers who write these assertions run into the same few questions.
7.1. Does JUnit have an assertEquals() that ignores order?
No. JUnit 5 and JUnit 6 compare lists position by position. We either sort both lists or compare frequency maps before calling assertEquals(), or use AssertJ or Hamcrest.
7.2. What is the difference between containsExactlyInAnyOrder() and containsAll()?
The method containsExactlyInAnyOrder() fails when the actual list has extra elements or a different count of an element. A containsAll() check passes as long as each expected value appears at least once, so [a, b, c] contains all of [a, b].
7.3. How do we compare two lists of objects ignoring order and some fields?
We extract the fields that matter with AssertJ’s extracting(), or we use usingRecursiveFieldByFieldElementComparatorIgnoringFields(“id”) before containsExactlyInAnyOrderElementsOf().
7.4. Do the lists need the same type for these assertions?
No. All the assertions compare elements, not list classes, so an ArrayList can be checked against a List.of() list or a LinkedList.
8. Conclusion
To assert two lists are equal ignoring order, AssertJ’s containsExactlyInAnyOrderElementsOf() is the clearest choice, and Hamcrest’s containsInAnyOrder() and Commons’ isEqualCollection() give the same result. All three compare the counts of duplicate elements, whereas hasSameElementsAs(), HashSet comparisons and containsAll() do not.
With JUnit alone, we compare sorted copies or frequency maps, because JUnit 6 has no order-free list assertion. For lists of objects, we extract the fields that matter, so generated IDs and row order do not make the test flaky. Comparing lists outside of tests is covered in comparing two lists in Java, and the ArrayList guide covers the list API itself.
9. References
- AssertJ AbstractIterableAssert Javadoc
- Hamcrest 3.0 Matchers Javadoc
- JUnit Assertions Javadoc
- Apache Commons Collections CollectionUtils
Happy Learning !!
How do you make sure that lists are different by at least one element?
If the lists are non-empty and not-equal then it means they are different by at least one element. What am I missing here?