Common Mistakes in Java Collections (and How to Avoid Them)

Illustration for Common Mistakes in Java Collections (and How to Avoid Them)
By Last updated:

Java Collections are a foundational part of Java programming, enabling developers to manage and manipulate data effectively. But even experienced developers fall into traps when using List, Set, Map, or concurrent collections. These mistakes can cause memory leaks, race conditions, poor performance, or incorrect results in production.

This tutorial identifies common Java Collections mistakes, provides detailed explanations, and shows you how to fix or avoid them using modern Java practices from Java 8 to Java 21.


Core Concepts

Java Collections Framework provides interfaces like List, Set, Map, and their implementations like ArrayList, HashSet, and HashMap. It supports:

  • Indexed collections (List)
  • Unique elements (Set)
  • Key-value mapping (Map)
  • Thread-safe variants (ConcurrentHashMap, CopyOnWriteArrayList)
  • Immutable versions (List.of(), Map.of())

Top Common Mistakes

1. Using == Instead of .equals()

Set<String> set = new HashSet<>();
set.add("hello");
System.out.println(set.contains(new String("hello"))); // false if `==` used

✅ Use .equals() for logical comparison.


2. Modifying Collection During Iteration

for (String s : list) {
    if (s.equals("delete")) list.remove(s); // ConcurrentModificationException
}

✅ Use Iterator.remove() or removeIf().


3. Choosing the Wrong Collection

  • ❌ Using List when duplicates aren't allowed.
  • ✅ Use Set for uniqueness.

4. Not Using Generics

List list = new ArrayList(); // Raw type

✅ Use:

List<String> list = new ArrayList<>();

5. Ignoring HashCode/Equals Contracts

class Person { String name; }
Set<Person> set = new HashSet<>();

✅ Always override equals() and hashCode().


6. Using Legacy Types

  • Vector, Hashtable
  • ✅ Use ArrayList, ConcurrentHashMap

7. Not Initializing Capacity Properly

new HashMap<>(); // Default capacity 16

✅ Use:

new HashMap<>(expectedSize / 0.75f + 1);

Internal Implementation and Memory Model

  • ArrayList resizes by 50%—avoid frequent resizes by pre-sizing.
  • HashMap switches from linked lists to red-black trees on hash collisions.
  • ConcurrentHashMap uses segmented locking for thread-safety.

Performance Considerations

Operation ArrayList LinkedList HashSet TreeSet
Add O(1)* O(1) O(1) O(log n)
Remove O(n) O(1) O(1) O(log n)
Contains O(n) O(n) O(1) O(log n)

Comparisons and Best Choices

  • Need ordering? → Use LinkedHashMap, TreeSet
  • Need thread safety? → Use ConcurrentHashMap, CopyOnWriteArrayList
  • Need immutability? → Use List.of(), Set.copyOf()

Version Differences in Java 8–21

  • Java 8: Introduced Stream, Optional, Collectors
  • Java 9: List.of(), Map.of(), immutable collections
  • Java 10: Type inference with var
  • Java 21: Performance tuning, structured concurrency

Functional Programming Pitfalls

  • ❌ Using map() where flatMap() is needed
  • ❌ Using stream().filter() on null collections (throws NPE)
  • ✅ Always check for null or use Optional

Anti-patterns and Fixes

  • ❌ Nested if checks for null maps ✅ Use Map.computeIfAbsent()

  • ArrayList for frequent removals at head ✅ Use LinkedList or Deque


Legacy Refactoring Tips

Before

Vector<String> list = new Vector<>();

After

List<String> list = new CopyOnWriteArrayList<>();

Best Practices Summary

  • Use immutable collections for read-only data
  • Use generics to prevent runtime ClassCastException
  • Don’t expose modifiable collections from APIs
  • Prefer Streams for transformation logic
  • Use Set for uniqueness, List for order, Map for key-value mappings

📌 What's New in Java Versions?

  • Java 8
    • Stream, Collectors.toList(), Optional, lambdas
  • Java 9
    • List.of(), Map.of() – true immutability
  • Java 10
    • var keyword for type inference
  • Java 21
    • HashMap performance boosts, better tree binning

Conclusion and Key Takeaways

Mastering Java Collections means not just knowing how to use them—but understanding how to use them right. Avoiding common mistakes in choice, iteration, comparison, and thread-safety will make your Java code more robust, performant, and maintainable.


FAQ

1. Why is == not reliable for Strings?

It compares object references. Use .equals() for value comparison.

2. Can I add null to a HashSet?

Yes, one null is allowed.

3. What’s the default capacity of ArrayList?

10 (Java 8+)

4. Should I use Vector?

No. Use ArrayList with synchronization or CopyOnWriteArrayList.

5. Is HashMap ordered?

No. Use LinkedHashMap for insertion order.

6. What causes ConcurrentModificationException?

Modifying a collection during iteration.

7. How to avoid resizing in HashMap?

Initialize it with a proper capacity.

8. What’s the difference between List.of() and Arrays.asList()?

List.of() returns an immutable list; Arrays.asList() is fixed-size and modifiable.

9. Can I use stream() on null collections?

No. It throws NullPointerException.

10. What’s the difference between HashSet and TreeSet?

HashSet is unordered and faster; TreeSet is sorted and uses more memory.