How to remove duplicates from a List in Java 25

Removing duplicate values from a Java List is a common data-cleaning task. Java provides several good ways to do it, and the best choice depends on whether you need to preserve encounter order, whether you want a List or a Set as the result, and whether you need custom duplicate-handling logic.

Four useful approaches are:

  1. Use Stream.distinct() and toList().
  2. Construct a HashSet when ordering does not matter.
  3. Construct a LinkedHashSet when encounter order should be preserved.
  4. Write a custom loop when deduplication requires additional business logic.

Remove duplicates with Stream.distinct()

For many applications, the cleanest way to deduplicate a List is to stream its elements, call distinct(), and collect the result with toList(). The stream preserves encounter order for an ordered source such as a List.

void main() {
    var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1);

    var unique = numbers.stream()
                        .distinct()
                        .toList();

    IO.println(numbers.size());
    IO.println(unique.size());
    IO.println(unique);
}

With Java 25 compact source files, no explicit class declaration or public static void main(String[] args) method is required for a small example like this. The new IO.println method also keeps console output concise.

The output is:

10
6
[0, 1, 2, 3, 5, 6]

One important detail is that Stream.toList() returns an unmodifiable List. If the deduplicated result must later be changed, create a mutable ArrayList from it.

var unique = new ArrayList<>(
    numbers.stream()
           .distinct()
           .toList()
);

Remove duplicates with HashSet

A Set does not permit duplicate elements, so constructing a HashSet from a List automatically removes duplicates.

void main() {
    var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1);

    var unique = new HashSet<>(numbers);

    IO.println(numbers.size());
    IO.println(unique.size());
    IO.println(unique);
}

This is simple and efficient when you actually want a Set. However, a HashSet does not promise to preserve the List's encounter order.

If you need a List again, construct one directly from the Set:

var uniqueList = new ArrayList<>(new HashSet<>(numbers));

This is preferable to converting the Set to an array and passing that array to List.of().

Preserve order with LinkedHashSet

If you want Set-based deduplication while preserving the order in which values first appear, use a LinkedHashSet.

void main() {
    var numbers = List.of(3, 1, 3, 2, 1, 5, 2);

    var unique = new ArrayList<>(
        new LinkedHashSet<>(numbers)
    );

    IO.println(unique);
}

The output preserves the first occurrence of each element:

[3, 1, 2, 5]

Remove duplicates with a custom Java loop

A custom loop is useful when deduplication must perform extra processing. A naive implementation can keep a second List and call contains() before each insertion:

void main() {
    var items = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1);
    var deduped = new ArrayList<Integer>();

    for (var item : items) {
        if (!deduped.contains(item)) {
            deduped.add(item);
        }
    }

    IO.println(deduped);
}

This version is easy to understand, but it can become inefficient for large Lists because ArrayList.contains() performs a linear search. Repeating that search for every element can lead to quadratic behavior.

Optimize a custom deduplication algorithm

If you need custom processing but also want fast membership checks, track values with a Set. There is usually no reason to use a HashMap merely to determine whether an element has already been encountered.

void main() {
    var items = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1);

    var seen = new HashSet<Integer>();
    var deduped = new ArrayList<Integer>();

    for (var item : items) {
        if (seen.add(item)) {
            deduped.add(item);
        }
    }

    IO.println(deduped);
}

The useful trick here is that Set.add() returns true only when the Set did not already contain the element. That means a separate contains() lookup is unnecessary.

Which Java List deduplication approach is best?

There is no single fastest approach for every workload, so avoid assuming that Stream.distinct() is automatically faster than every Set-based solution. Choose based on the semantics your program needs, and benchmark representative data if performance is important.

  • Stream.distinct().toList() is concise and preserves encounter order for an ordered stream.
  • HashSet is a natural choice when uniqueness matters and encounter order does not.
  • LinkedHashSet combines uniqueness with predictable encounter order.
  • A custom loop plus a Set is useful when duplicate removal requires additional logic.

For straightforward Java 25 code where the result should remain a List, stream().distinct().toList() is often the most readable solution. When the collection itself should enforce uniqueness, use a Set instead of repeatedly deduplicating a List.