How to Find the Difference Between Two Lists in Java

Java's Collections API has a method built specifically for this - removeAll - but reaching for it without thinking about what it removes against can quietly turn a simple operation into a slow one on large lists. Here's the built-in approach, why it can be slow, and three faster or cleaner alternatives.

Quick answer

new ArrayList<>(a).removeAll(new HashSet<>(b)) gives you a-only without mutating the original a, and is roughly O(n + m) because HashSet lookups are O(1). Calling removeAll(b) directly on a plain List instead of a HashSet is O(n * m) since each check scans b linearly. Streams give the same result functionally: a.stream().filter(x -> !setB.contains(x)).collect(Collectors.toList()).

Need the same thing in another language?

See the full reference for Python, JavaScript, SQL, Bash, PowerShell, and more.

See every language →

removeAll - the built-in method, and its trap

List<String> a = new ArrayList<>(List.of("apple", "banana", "cherry"));
List<String> b = List.of("banana", "date");

a.removeAll(b);
System.out.println(a); // [apple, cherry]

This mutates a directly, which is fine if you don't need the original list afterward. The performance trap: if b is a plain List (as it is above), removeAll scans it linearly for every element of a, making the whole call O(n * m).

HashSet-based subtraction - the fast, non-mutating version

List<String> a = List.of("apple", "banana", "cherry");
List<String> b = List.of("banana", "date");

List<String> aOnly = new ArrayList<>(a);
aOnly.removeAll(new HashSet<>(b));

System.out.println(aOnly); // [apple, cherry]
System.out.println(a);     // [apple, banana, cherry] - unchanged

Two changes fix both problems from the naive version: copying a into a new ArrayList first means the original a stays intact, and wrapping b in a HashSet makes each contains check O(1) instead of O(m), bringing the whole operation down to roughly O(n + m).

Using Streams

List<String> a = List.of("apple", "banana", "cherry");
List<String> b = List.of("banana", "date");

Set<String> setB = new HashSet<>(b);
List<String> aOnly = a.stream()
    .filter(x -> !setB.contains(x))
    .collect(Collectors.toList());

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

Functionally identical to the HashSet approach above, but written as an explicit filter rather than a mutating removeAll call - a style many teams prefer for readability, and it composes cleanly with other stream operations like sorting or mapping in the same pipeline.

Apache Commons CollectionUtils

import org.apache.commons.collections4.CollectionUtils;

List<String> a = List.of("apple", "banana", "cherry");
List<String> b = List.of("banana", "date");

Collection<String> aOnly = CollectionUtils.subtract(a, b);
System.out.println(aOnly); // [apple, cherry]

If your project already depends on Apache Commons Collections, CollectionUtils.subtract is a one-line alternative that respects duplicate counts (a multiset subtraction) rather than treating each item as simply present or absent. Not worth adding the dependency solely for this method.

Just need the answer, not the code? Paste both lists.

Open Compare 2 Lists →

Frequently Asked Questions

What is the simplest way to find the difference between two lists in Java?
List<String> aOnly = new ArrayList<>(a); aOnly.removeAll(b); is the simplest, built directly into the Collections API. It mutates a copy of a in place rather than a new immutable list, and its performance depends heavily on what type b is.
Why is List.removeAll(list) slow?
If b is a plain ArrayList, removeAll has to scan it linearly for every element it checks, making the whole call O(n * m). Wrapping b in a HashSet first - aOnly.removeAll(new HashSet<>(b)) - drops each containment check to O(1), making the overall operation roughly O(n + m).
How do I keep the original list a unmutated?
removeAll mutates whatever list you call it on, so copy first: List<String> aOnly = new ArrayList<>(a); aOnly.removeAll(setB);. This leaves the original a untouched and returns the difference in a new list.
How do I get the difference between two lists using Java Streams?
Set<String> setB = new HashSet<>(b); List<String> aOnly = a.stream().filter(x -> !setB.contains(x)).collect(Collectors.toList());. This is functionally equivalent to the HashSet-based removeAll approach but doesn't mutate anything and reads more explicitly for anyone scanning the code.
What is symmetric difference in Java?
There's no built-in method for it. Build it from two filtered lists: filter a for items not in setB, filter b for items not in setA, and concatenate the two results with Stream.concat or by adding both lists together.
Does Apache Commons have a list difference utility?
Yes - CollectionUtils.subtract(a, b) from Apache Commons Collections returns a-only as a new collection without mutating either input, and it's a multiset subtraction (it respects duplicate counts) rather than a plain set difference.
How do I compare two lists of custom objects by a specific field in Java?
Extract the field into a Set first: Set<Long> idsB = b.stream().map(Person::getId).collect(Collectors.toSet()); List<Person> aOnly = a.stream().filter(p -> !idsB.contains(p.getId())).collect(Collectors.toList());. This avoids relying on the object's equals()/hashCode() implementation, which may not compare the field you actually care about.

Share this tool

Featured on ToolDirs
© 2026 ListDiff

Free online list and text processing tools.