Tutorial

How to Compare Two Lists in Java (Common, Missing and Equal Elements)

By 10 min read

Comparing two lists comes up all the time in Java: syncing database records, checking API responses in tests, or finding which IDs were added or removed between two exports. Depending on the question, you need a different tool:

  • Are the lists equal? – with or without caring about order and duplicates
  • Which elements are in both lists? – the intersection
  • Which elements are only in one list? – the difference

This guide covers each case with the standard library (Java 16+ for Stream.toList() and records), plus Apache Commons and Guava alternatives. All snippets use these imports:

import java.util.*;
import java.util.stream.*;

Only need to compare two lists once? Paste them into the free Compare Two Lists tool to see what's only in A, only in B and in both – no code required.


1. Check whether two lists are equal

List.equals() returns true only when both lists contain the same elements in the same order:

List<Integer> a = List.of(1, 2, 3);
List<Integer> b = List.of(1, 2, 3);
List<Integer> c = List.of(3, 2, 1);

System.out.println(a.equals(b)); // true
System.out.println(a.equals(c)); // false – same elements, different order

It compares elements with their own equals() method, so it works for strings, numbers and any class that implements equals() properly. It also works across implementations: an ArrayList and a LinkedList with the same elements are equal.


2. Compare lists ignoring order

How you ignore order depends on whether duplicates matter.

Same elements and same counts (duplicates matter) – sort copies and compare them:

List<Integer> x = Arrays.asList(3, 1, 2, 2);
List<Integer> y = Arrays.asList(2, 3, 2, 1);

List<Integer> sortedX = new ArrayList<>(x);
List<Integer> sortedY = new ArrayList<>(y);
Collections.sort(sortedX);
Collections.sort(sortedY);

System.out.println(sortedX.equals(sortedY)); // true

If the elements can't be sorted, compare frequency maps instead:

Map<Integer, Long> countX = x.stream()
        .collect(Collectors.groupingBy(i -> i, Collectors.counting()));
Map<Integer, Long> countY = y.stream()
        .collect(Collectors.groupingBy(i -> i, Collectors.counting()));

System.out.println(countX.equals(countY)); // true

Same distinct elements (duplicates ignored) – compare sets:

List<Integer> z = List.of(1, 2, 3);
System.out.println(new HashSet<>(x).equals(new HashSet<>(z))); // true

3. Find common elements (intersection) with retainAll

retainAll() removes every element that is not in the other collection. It modifies the list it's called on, so work on a copy:

List<String> listA = List.of("apple", "banana", "cherry", "date");
List<String> listB = List.of("banana", "date", "fig");

List<String> common = new ArrayList<>(listA);
common.retainAll(new HashSet<>(listB));

System.out.println(common); // [banana, date]

Copying into a new ArrayList matters for a second reason: lists created with List.of() or Arrays.asList() don't support removal, and calling retainAll() on them throws UnsupportedOperationException.


4. Find missing elements (difference) with removeAll

removeAll() is the opposite: it removes everything that is in the other collection, leaving the elements unique to the first list:

List<String> onlyInA = new ArrayList<>(listA);
onlyInA.removeAll(new HashSet<>(listB));
System.out.println(onlyInA); // [apple, cherry]

List<String> onlyInB = new ArrayList<>(listB);
onlyInB.removeAll(new HashSet<>(listA));
System.out.println(onlyInB); // [fig]

Both methods keep the original order of the list you start from, and they keep duplicates of elements that survive.


5. The same with streams

Streams don't modify the source lists and read nicely when you need extra filtering:

Set<String> setB = new HashSet<>(listB);

List<String> onlyInA = listA.stream()
        .filter(item -> !setB.contains(item))
        .toList();                       // Java 16+; use collect(Collectors.toList()) before that

List<String> inBoth = listA.stream()
        .filter(setB::contains)
        .toList();

System.out.println(onlyInA); // [apple, cherry]
System.out.println(inBoth);  // [banana, date]

To get the elements that are in exactly one of the lists (the symmetric difference), combine both directions:

Set<String> setA = new HashSet<>(listA);

List<String> inOnlyOne = Stream.concat(
        listA.stream().filter(item -> !setB.contains(item)),
        listB.stream().filter(item -> !setA.contains(item)))
        .toList();

System.out.println(inOnlyOne); // [apple, cherry, fig]

For a case-insensitive comparison, normalize before building the set – for example listB.stream().map(String::toLowerCase).collect(Collectors.toSet()) – and lowercase each item you check against it.


6. Compare lists of objects

Collections compare objects with equals() and, for hash-based collections, hashCode(). Java records generate both from their fields, so they work out of the box:

record Customer(int id, String name) {}

List<Customer> oldCustomers = List.of(new Customer(1, "Ann"), new Customer(2, "Bob"));
List<Customer> newCustomers = List.of(new Customer(2, "Bob"), new Customer(3, "Cy"));

Set<Customer> newSet = new HashSet<>(newCustomers);
List<Customer> removed = oldCustomers.stream()
        .filter(c -> !newSet.contains(c))
        .toList();

System.out.println(removed); // [Customer[id=1, name=Ann]]

For a regular class, override both equals() and hashCode() – if you only override equals(), HashSet lookups won't find matching objects.

Often you want to compare objects by a key instead of all fields, so that a changed name doesn't count as a different customer:

Set<Integer> newIds = newCustomers.stream()
        .map(Customer::id)
        .collect(Collectors.toSet());

List<Customer> removedById = oldCustomers.stream()
        .filter(c -> !newIds.contains(c.id()))
        .toList();

System.out.println(removedById); // [Customer[id=1, name=Ann]]

7. Library helpers: Apache Commons and Guava

If your project already uses one of these libraries, they offer one-line set operations.

Apache Commons Collections (org.apache.commons.collections4.CollectionUtils):

Collection<String> onlyInA = CollectionUtils.subtract(listA, listB);     // [apple, cherry]
Collection<String> inBoth = CollectionUtils.intersection(listA, listB);  // banana, date (any order)
Collection<String> inOnlyOne = CollectionUtils.disjunction(listA, listB); // apple, cherry, fig (any order)

Guava (com.google.common.collect.Sets) works on sets and returns live views:

Set<String> setA = new HashSet<>(listA);
Set<String> setB = new HashSet<>(listB);

Set<String> onlyInA = Sets.difference(setA, setB);
Set<String> inBoth = Sets.intersection(setA, setB);
Set<String> inOnlyOne = Sets.symmetricDifference(setA, setB);

CollectionUtils respects how many times each element occurs; Guava's Sets work on distinct elements. Copy a Guava view with new HashSet<>(view) if the underlying sets will change.


8. Performance: don't search a List inside a loop

list.contains() checks elements one by one, so ArrayList.removeAll(otherList) and filter(otherList::contains) compare every element of one list with every element of the other. For two lists of 100,000 items that can mean up to 10 billion comparisons.

Wrapping the other list in a HashSet first – as all the examples above do – turns each lookup into a constant-time operation, so the whole comparison takes roughly 100,000 steps:

// Slow: O(n × m)
listA.stream().filter(listB::contains).toList();

// Fast: O(n + m)
Set<String> lookup = new HashSet<>(listB);
listA.stream().filter(lookup::contains).toList();

Summary

Goal Recommended approach Keeps order? Notes
Equal, same order a.equals(b) – Works across List implementations
Equal, any order, with duplicates Sort copies or compare frequency maps – Maps work for unsortable elements
Equal, any order, no duplicates new HashSet<>(a).equals(new HashSet<>(b)) – Ignores repeated elements
Common elements copy.retainAll(new HashSet<>(b)) Yes Copy first; List.of is immutable
Only in A copy.removeAll(new HashSet<>(b)) or stream filter Yes Stream version doesn't modify anything
In exactly one list Stream.concat of both differences Yes Or CollectionUtils.disjunction
Lists of objects Records, or equals() + hashCode() Yes Or compare by a key such as id

Start with a HashSet lookup and a stream filter – it's fast, doesn't modify your lists, and handles almost every case. For quick one-off checks outside your code, the online list comparison tool does the same in your browser. Working in Python instead? See How to Compare Two Lists in Python.

Tags

#Java#List Comparison#Tutorial

About the author

songshiqiangIndependent developer

Builds fun websites with AI tools.

Related Posts

Tutorial

How to Compare Two Lists in Google Sheets (Matches and Differences)

Compare two lists in Google Sheets with conditional formatting, COUNTIF, MATCH, XLOOKUP and FILTER: highlight matches, flag missing items, extract differences into a new list, and compare lists on different sheets.

9 min read
#Google Sheets#List Comparison#Tutorial
Read More
Tutorial

How to Compare Two Lists in Python (With Examples)

A complete guide to comparing two lists in Python: find common and different elements with sets, keep order with list comprehensions, check if lists are equal, compare element by element, compare lists of dictionaries, and handle large data with pandas.

12 min read
#Python#List Comparison#Tutorial
Read More
Tutorial

How to Compare Two Lists in Excel (Find Matches and Differences)

Nine ways to compare two lists in Excel: highlight matches with conditional formatting, flag them with COUNTIF or MATCH, look them up with VLOOKUP or XLOOKUP, extract them with FILTER, compare row by row, and use Power Query for large data.

12 min read
#Excel#List Comparison#XLOOKUP#Tutorial
Read More