Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

A Java Set already prevents duplicate elements. If you have a collection with repeats, create a set from it; use LinkedHashSet when you need to keep the input’s first-seen order. If a set seems to contain duplicates, check how equality is defined for its elements or comparator.

Remove duplicates from a collection

For a list or another collection that may contain repeated values, pass it to a set constructor:

List<Integer> numbers = List.of(1, 2, 2, 3, 3, 3);
Set<Integer> unique = new HashSet<>(numbers);

The constructor adds the source elements to a new set, so equal values appear only once. It does not modify numbers, and the result is a Set, not a List. A HashSet does not guarantee iteration order, so do not rely on the order in which its elements print or are traversed. The Java Collections [Set tutorial](https://docs.oracle.com/javase/tutorial/collections/interfaces/set.html) describes this conversion pattern and the standard set implementations.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the original order

When you want to remove repeats but retain the first occurrence of each value, use LinkedHashSet:

List<String> names = List.of("Ana", "Ben", "Ana", "Cara", "Ben");
List<String> uniqueNames = new ArrayList<>(new LinkedHashSet<>(names));

System.out.println(uniqueNames); // [Ana, Ben, Cara]

LinkedHashSet iterates in insertion order; adding a value already present does not move its existing entry. This makes it a practical choice for converting a list to a unique list while preserving its order. See the [Java API documentation for LinkedHashSet](https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/LinkedHashSet.html).

Deduplicate with streams

Use distinct() when you want a stream pipeline to eliminate repeated values according to their equality semantics:

List<String> uniqueNames = names.stream()
        .distinct()
        .toList();

Stream.toList() is available from Java 16. For an earlier target Java version, collect to a list instead:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
List<String> uniqueNames = names.stream()
        .distinct()
        .collect(Collectors.toList());

For a set result without a required iteration order:

Set<String> unique = names.stream()
        .collect(Collectors.toSet());

Do not assume a particular iteration order for the set returned by Collectors.toSet(). Request an insertion-ordered set explicitly when that matters:

Set<String> unique = names.stream()
        .collect(Collectors.toCollection(LinkedHashSet::new));

For an ordered stream, distinct() retains the first occurrence in encounter order. Avoid promising that presentation order for an unordered or parallel pipeline unless you have deliberately specified and preserved the ordering you need.

Sort while removing duplicates

Use TreeSet when you want unique values in sorted order:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Set<String> sortedUnique = new TreeSet<>(names);

A TreeSet uses natural ordering or a supplied comparator. Unlike a hash-based set, it treats two entries as equivalent for set operations when comparison returns 0. That can differ from equals(); for example, a case-insensitive comparator can treat "Java" and "java" as one entry. Use this behavior intentionally. The [TreeSet API](https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/TreeSet.html) documents its ordering rules.

Custom objects: define equality correctly

For HashSet and LinkedHashSet, Java decides whether two objects are duplicates using equals() and hashCode(), not their printed text or whichever field happens to look identical. If users are duplicates by ID, make that identity rule explicit and implement both methods consistently:

final class User {
    private final long id;
    private final String email;

    User(long id, String email) {
        this.id = id;
        this.email = email;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof User user)) return false;
        return id == user.id;
    }

    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }
}

Now two User instances with the same ID compare equal, even if their email fields differ, and a hash-based set retains only one of them. Overriding just one of equals() and hashCode() is not correct for hash-based collections. Base both on the same stable identity fields; changing those fields while an object is in a hash-based set can make later lookup or removal behave unexpectedly. The [Java Set API](https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/Set.html) defines the no-duplicates contract in terms of equality and describes the hash-code requirement.

Deduplicate by one field without changing object equality

Sometimes objects have a normal equality definition, but one operation should keep only one record per email, ID, or other key. Use a map and decide which record wins. To keep the first record for each email:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Map<String, User> byEmail = new LinkedHashMap<>();

for (User user : users) {
    byEmail.putIfAbsent(user.getEmail(), user);
}

List<User> uniqueUsers = new ArrayList<>(byEmail.values());

To keep the last record for each email, replace putIfAbsent with put. This makes the duplicate-resolution policy explicit instead of changing User.equals() for one particular task.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Why does my set appear to have duplicates?

A correctly functioning set cannot contain duplicates under its own membership rules. If output looks repetitive, check these possibilities:

  • The source is not actually a set. A list, array, database result, or stream can contain repeated values. Confirm the runtime type as well as the declared type.
  • Objects print alike but are not equal. Two objects can have the same displayed name yet differ by ID, or use the default identity-based equality. Inspect the fields that define identity and the class’s equals() and hashCode().
  • Equality or hashing is inconsistent. In a hash-based set, equal objects must have equal hash codes. Implement both methods together.
  • An identity field changed after insertion. Prefer immutable identity fields while objects are stored in a hash-based set.
  • A TreeSet comparator defines a different equivalence rule. Check whether the comparator returns zero for values you expect to keep separately, or nonzero for values you intend to collapse.
  • Text differs in formatting. Case, spaces, or Unicode representation can make strings distinct. Normalize only if those differences should not matter to your application.

For example, trim and lowercase strings before collecting them if that is the intended definition of a duplicate:

Set<String> normalized = raw.stream()
        .map(String::trim)
        .map(String::toLowerCase)
        .collect(Collectors.toCollection(LinkedHashSet::new));

This changes the values and can erase meaningful distinctions, so apply normalization only when it matches your data rules.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Nulls, immutable sets, and common traps

  • null depends on the implementation. HashSet and LinkedHashSet permit one null; the Set interface allows implementations to reject it. A naturally ordered TreeSet generally cannot accept null.
  • Do not use Set.of(...) to deduplicate arbitrary input. Static set factories are for known unique values and reject duplicate arguments rather than silently removing them. Use a constructor such as new LinkedHashSet<>(source) for duplicate-containing input. See the [Java Set API](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/util/Set.html).
  • Unmodifiable results cannot be edited in place. Make a new set from the source, then wrap it if needed: Collections.unmodifiableSet(new LinkedHashSet<>(source)). Check the API contract for the Java version you target before using a factory method.
  • Do not clear and refill a shared set as a shortcut. Besides requiring a mutable set, separate clear() and addAll() calls can expose an empty or partial state to other threads; they are not one atomic replacement.

Choose the right approach

Requirement Use
Remove repeats; order does not matter new HashSet<>(source)
Keep first-seen order new LinkedHashSet<>(source)
Sort and remove repeats new TreeSet<>(source), with its comparator semantics in mind
Deduplicate in a stream distinct() or a suitable collector
Deduplicate objects by one selected property A map keyed by that property, with an explicit first/last-wins rule

In short, if your input is a list, convert it to the set implementation that matches your ordering needs. If it is already a set, investigate its equality or comparator rules rather than trying to remove duplicates from it.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.