Finding duplicates in a Java List can mean two slightly different things:
- Find every repeated occurrence.
- Find only the unique values that appear more than once.
The best approach depends on which result you need and how large the list is.
1. Find duplicates with nested loops
The most direct approach compares each element with the elements that follow it.
var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1, 5);
var duplicates = new ArrayList<Integer>();
for (int x = 0; x < numbers.size(); x++) {
for (int y = x + 1; y < numbers.size(); y++) {
if (numbers.get(x).equals(numbers.get(y))) {
duplicates.add(numbers.get(x));
break;
}
}
}
System.out.println(duplicates);The output is:
[0, 1, 1, 5, 0]This approach is easy to understand, but its nested loops make it inefficient for large lists because the number of comparisons grows quickly.
2. Find unique duplicate values with a HashSet
If you only want each duplicated value once, store the result in a Set:
var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1, 5);
var duplicates = new HashSet<Integer>();
for (int x = 0; x < numbers.size(); x++) {
for (int y = x + 1; y < numbers.size(); y++) {
if (numbers.get(x).equals(numbers.get(y))) {
duplicates.add(numbers.get(x));
break;
}
}
}
System.out.println(duplicates);A possible result is:
[0, 1, 5]A HashSet does not guarantee iteration order. If deterministic insertion order matters, use LinkedHashSet.
3. Find duplicate occurrences in one pass
A more efficient solution takes advantage of the return value from Set.add(). It returns false when the value is already present.
var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1, 5);
var seen = new HashSet<Integer>();
var duplicates = new ArrayList<Integer>();
for (var number : numbers) {
if (!seen.add(number)) {
duplicates.add(number);
}
}
System.out.println(duplicates);The output is:
[1, 0, 0, 1, 5]This version performs one pass over the list and is generally a better choice than nested loops.
If you want only the unique duplicate values, make duplicates a Set too:
var seen = new HashSet<Integer>();
var duplicates = new LinkedHashSet<Integer>();
for (var number : numbers) {
if (!seen.add(number)) {
duplicates.add(number);
}
}4. Find duplicates with a Java 15 Stream
The same one-pass technique can be used with a stream. Because this article targets Java 15, use collect(Collectors.toList()). The later convenience method Stream.toList() was not yet available in Java 15.
var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1, 5);
var seen = new HashSet<Integer>();
var duplicates = numbers.stream()
.filter(number -> !seen.add(number))
.collect(Collectors.toList());
System.out.println(duplicates);The output is:
[1, 0, 0, 1, 5]This is concise, but the stream predicate has a side effect because it mutates seen. It is acceptable in a simple sequential stream, but it should not be used with parallelStream() unless the data structure and algorithm are redesigned for concurrency.
5. Count duplicates with Collections.frequency()
If you need to know how many times each value appears, Collections.frequency() provides a simple solution:
var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1, 5);
var unique = new LinkedHashSet<>(numbers);
for (var number : unique) {
int count = Collections.frequency(numbers, number);
System.out.println(number + " occurrences: " + count);
}The output is:
0 occurrences: 3
1 occurrences: 3
2 occurrences: 1
3 occurrences: 1
5 occurrences: 2
6 occurrences: 1This approach is easy to read, but Collections.frequency() scans the list for each unique value. For large lists, a frequency map is more efficient.
Use a HashMap for efficient frequency counting
A frequency map counts every value in a single pass:
var numbers = List.of(0, 1, 1, 2, 3, 5, 6, 0, 0, 1, 5);
var counts = new LinkedHashMap<Integer, Integer>();
for (var number : numbers) {
counts.merge(number, 1, Integer::sum);
}
counts.forEach((number, count) ->
System.out.println(number + " occurrences: " + count)
);To extract only values that occur more than once:
var duplicates = counts.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
System.out.println(duplicates);The result is:
[0, 1, 5]Which Java duplicate-finder should you use?
| Approach | Best use |
|---|---|
| Nested loops | Simple teaching example or very small lists |
| HashSet one-pass loop | Fast duplicate detection |
| Stream + HashSet | Concise sequential processing |
| Collections.frequency() | Small lists where readability matters most |
| HashMap frequency count | Large lists or when occurrence counts are required |
For most applications, the one-pass HashSet approach is the simplest efficient way to detect duplicates. If you also need occurrence counts, use a Map and merge().
The important distinction is whether you want every repeated occurrence, the unique set of duplicated values, or the frequency of each value. Once that requirement is clear, the Java collection API provides a straightforward solution.