Java Collections are the standard toolkit for storing and processing groups of objects—lists, sets, maps, queues, and more. If you can choose the right collection and use it correctly, your code becomes both faster and safer.
This Java Collections tutorial is a practical reference: it covers the core interfaces, the most important implementations, how iteration and ordering work, and the performance/thread-safety details that usually cause production bugs.
As an Amazon Associate I earn from qualifying purchases.
You’ll also get copy-paste examples (Java 17-style) plus a troubleshooting checklist for the most common failures like ConcurrentModificationException, incorrect map lookups, and ordering surprises.
Recommended Free Tools
What Java Collections are (and why they matter)
The Java Collections Framework (Java SE) provides reusable data structures with consistent APIs. You interact with collections through interfaces like List, Set, and Map, while concrete classes decide the real storage and performance behavior.
Why it matters: the wrong choice can turn an O(1) operation into an O(n) one, or cause subtle bugs when equality, hashing, ordering, or concurrency aren’t handled properly.
Prerequisites and setup
You should be comfortable with basic Java: classes, generics (<T>), and common language constructs like for loops and lambdas. The examples below use Java 17 syntax and standard library classes available in Java 8+.
Optional but helpful: an IDE (IntelliJ IDEA, Eclipse, or VS Code) and familiarity with running unit tests via JUnit. For this tutorial, no external libraries are required.
Core interfaces you must know
Most “collection” problems start with picking the wrong abstraction. Here are the interfaces that show up everywhere in real code.
Collection
Collection<E> is the root interface for most collections. It supports basic operations like add, remove, contains, and iteration.
Use it when order doesn’t matter and duplicates are allowed (or you simply want the generic collection behavior).
List
List<E> preserves insertion order and allows duplicates. It also supports index-based access via get(int).
Free tools Windows power users keep installed
One-click scans. No signup required.
Use List when you care about positions: e.g., ordered steps, ranked items, or “first/second/third” logic.
Set
Set<E> does not allow duplicates. Uniqueness depends on equals and hashCode for hash-based sets.
Use Set when you want to deduplicate or enforce “membership” semantics.
Queue / Deque
Queue<E> represents FIFO processing, while Deque<E> supports double-ended operations (push/pop at both ends).
Use queues for task processing and producer/consumer pipelines. Use deques for stacks (LIFO) and efficient “worklist” patterns.
Common implementations and when to use them
This is the core “what class should I instantiate?” section. Use the following implementations as your default toolbox.
ArrayList
ArrayList is backed by a resizable array. It’s usually the best default for a List.
Fast random access: get(i) is typically O(1). Appending is amortized O(1). Middle inserts/removes are O(n) due to shifting.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsLinkedList
LinkedList is a doubly-linked list that implements List and Deque.
It can make head/tail insertions easy, but random access is slow (linear). In practice, it’s rarely faster than ArrayList unless you’re doing lots of deque-like operations at the ends.
HashSet
HashSet stores unique elements using hashing. Average add, remove, and contains are O(1).
Correctness depends on stable equals/hashCode. Don’t mutate fields that affect hashing while an object is in the set.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchTreeSet
TreeSet stores unique elements in sorted order using a Red-Black tree.
Operations are typically O(log n). Uniqueness is based on ordering rules: two elements considered “equal” by the comparator are treated as duplicates.
LinkedHashSet
LinkedHashSet preserves insertion order while still using hashing.
It’s a great choice when you want deterministic iteration (e.g., for UI display) and fast membership checks.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
HashMap
HashMap maps keys to values with hashing. Average performance for get, put, and containsKey is O(1).
Keys must have consistent equals/hashCode. Also note: one null key is allowed and multiple null values are allowed.
TreeMap
TreeMap stores keys in sorted order using a Red-Black tree.
Use it for range queries like “all entries between A and Z” or when you must iterate keys in order.
LinkedHashMap
LinkedHashMap preserves insertion order (or access order, if configured) while maintaining hash-based performance.
This class is commonly used for LRU caches and predictable iteration.
PriorityQueue
PriorityQueue is a heap-based queue. It doesn’t enforce FIFO; it always removes the element with the highest priority (based on natural order or a comparator).
Common for scheduling, Dijkstra-like algorithms, and “process the next best candidate” workflows.
Choosing the right collection: a practical decision guide
Instead of memorizing everything, use this selection logic. It’s tuned for day-to-day engineering where performance and correctness both matter.
Need fast random access?
Choose ArrayList. If you’re mostly reading and appending, it’s hard to beat.
Only switch if you have strong reasons (e.g., you need frequent middle insertions and your workload is proven).
Need fast inserts/removals in the middle?
Neither ArrayList nor LinkedList is perfect. For frequent middle edits, consider alternative designs (like using indices plus lazy deletion) or different data structures.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →If you’re doing many removals while iterating, you can also use removeIf or batch rebuild patterns rather than repeated shifting.
Need uniqueness?
Choose HashSet for speed unless you also need ordering or sorting.
If you need predictable iteration order, pick LinkedHashSet. If you need sorted uniqueness, pick TreeSet.
Need sorted order?
Choose TreeSet (for unique values) or TreeMap (for key/value pairs).
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →If sorting happens rarely but iteration needs order, you can also sort a list once and store it in an ArrayList.
Need predictable iteration order?
Choose LinkedHashMap or LinkedHashSet. Regular HashMap/HashSet does not guarantee ordering.
This matters a lot for deterministic tests and stable UI rendering.
Need FIFO or LIFO?
For FIFO: use ArrayDeque (usually) or LinkedList as a queue.
Free tools Windows power users keep installed
One-click scans. No signup required.
For priority-based ordering: use PriorityQueue. For LIFO: use ArrayDeque and operate from the same end.
Rank #3
- The Raspberry Pi Raphael Starter Kit for Beginners: The kit offers a rich learning experience for beginners aged 10+. With 337+ components, 161 projects, and 70+ expert-led video lessons, this kit makes learning Raspberry Pi programming and IoT engaging and accessible. Compatible with Raspberry Pi 5/4B/3B+/3B/Zero 2 W /400, RoHS Compliant
- Expert-Guided Video Lessons: The Raspberry Pi Kit includes 70+ video tutorials by the renowned educator, Paul McWhorter. His engaging style simplifies complex concepts, ensuring an effective learning experience in Raspberry Pi programming
- Wide Range of Hardware: The Raspberry Pi 5 Kit includes a diverse array of components like Camera, Speaker, sensors, actuators, LEDs, LCDs, and more, enabling you to experiment and create a variety of projects with the Raspberry Pi
- Supports Multiple Languages: The Raspberry Pi 4 Kit offers versatility with support for 5 programming languages - Python, C, Java, Node.js and Scratch, providing a diverse programming learning experience
- Dedicated Support: Benefit from our ongoing assistance, including a community forum and timely technical help for a seamless learning experience
Working with collections step-by-step
Now let’s go through the mechanics you’ll use every day: creating collections, adding elements, iterating, removing safely, and sorting.
Create and add elements
Prefer interfaces in variable declarations and concrete types in construction.
List<String> names = new ArrayList<>();
names.add("Ada");
names.add("Linus");
For maps:
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 10);
scores.put("Bob", 20);
Integer bob = scores.get("Bob"); // 20
For Java 9+ immutable collections:
List<String> fixed = List.of("x", "y", "z");
Set<Integer> ids = Set.of(1, 2, 3);
Map<String, Integer> defaults = Map.of("retries", 3, "timeoutMs", 2500);
Iterate safely (and fast)
For read-only iteration, the enhanced for loop is clean and fast enough in most cases.
for (String name : names) { System.out.println(name);
}
For index-based loops (when using List and random access matters):
for (int i = 0; i < names.size(); i++) { System.out.println(names.get(i));
}
For removal during iteration, don’t mutate the underlying collection directly with the iterator’s loop. Use removeIf (lists/sets/maps need different approaches).
Remove elements without bugs
Safe removal with removeIf (Java 8+):
names.removeIf(n -> n.startsWith("L"));
For maps, removal is typically done via entrySet() iteration and calling iterator.remove() or building a filtered map. Here’s the iterator pattern:
Iterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
while (it.hasNext()) { Map.Entry<String, Integer> e = it.next(); if (e.getValue() < 15) it.remove();
}
Sort and compare correctly
Use Collections.sort (legacy style) or List.sort (preferred). For custom objects, implement Comparable or provide a Comparator.
List<Person> people = new ArrayList<>();
people.sort(Comparator.comparingInt(Person::age));
// or descending
people.sort(Comparator.comparingInt(Person::age).reversed());
For sorted sets/maps, use the same comparator consistently. If you mix comparators or change sort keys, you can break expected ordering and uniqueness rules.
Iteration, ordering, and equality pitfalls
The most common production issues in collections aren’t syntax—they’re model mismatches between how the code thinks about equality, ordering, and iteration.
equals and hashCode for sets and maps
If you store custom objects in HashSet or HashMap, you must implement equals and hashCode correctly. A broken hash implementation looks like “random missing keys” or “duplicates that shouldn’t exist.”
Also follow this rule: once an object is in a hash-based collection, the fields used by equals/hashCode must not change.
Example (good practice pattern):
public final class User { private final String id; private final String email; public User(String id, String email) { this.id = id; this.email = email; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User other)) return false; return id.equals(other.id); } @Override public int hashCode() { return id.hashCode(); }
}
Comparator vs Comparable
Comparable defines natural ordering for a class. Comparator lets you define ordering externally.
For TreeSet/TreeMap, if you use a comparator, it becomes the source of truth for ordering and uniqueness. Two distinct objects can be treated as duplicates if the comparator returns 0.
Fail-fast iterators and ConcurrentModificationException
Java’s “fail-fast” iterators detect structural modification during iteration and throw ConcurrentModificationException. This is a best-effort safety mechanism, not a guarantee of correctness.
Common cause:
for (String s : names) { if (s.startsWith("A")) { names.remove(s); // will likely throw }
}
Fix: use removeIf or an iterator with iterator.remove().
Performance and memory: what to expect
Performance is about patterns: access frequency, data size, insert/remove frequency, and key hashing quality. Here are the practical expectations that matter.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Big-O cheat sheet
| Operation | ArrayList | LinkedList | HashMap / HashSet | TreeMap / TreeSet |
|---|---|---|---|---|
| Get by index | O(1) | O(n) | N/A | O(log n) |
| Insert/remove at start | O(n) | O(1) | N/A | O(log n) |
| Insert/remove in middle | O(n) | O(n) | N/A | O(log n) |
| Contains / lookup | O(n) | O(n) | Avg O(1) | O(log n) |
| Iterate all elements | O(n) | O(n) | O(n) | O(n) |
Capacity, resizing, and load factor
ArrayList and HashMap resize as they grow. You can reduce resizing overhead by pre-sizing when you know an approximate size.
HashMap uses a default initial capacity of 16 and a default load factor of 0.75. If you pass an expected size expectedSize, you can compute an initial capacity estimate.
int expectedSize = 10_000;
float loadFactor = 0.75f;
int initialCapacity = (int) (expectedSize / loadFactor) + 1;
Rank #4
Map<String, Integer> map = new HashMap<>(initialCapacity);
Autoboxing overhead
Be careful when using collections of primitives like int. Java collections store objects, so Integer requires boxing/unboxing.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchIf you do heavy numeric processing, consider specialized primitive collections from libraries (not part of core Java), or restructure code to reduce boxing hotspots.
Thread-safety and concurrent collections
Thread-safety is where “works on my machine” collection code tends to break. The default collections are not thread-safe for concurrent writes.
You have three broad options: external synchronization, synchronized wrappers, or concurrent collections from java.util.concurrent.
Synchronize vs concurrent implementations
External synchronization locks around all access; it’s simple but can reduce throughput.
Recommended Free Tools
Synchronized wrappers exist (e.g., Collections.synchronizedList), but you still need to synchronize during iteration to avoid inconsistent reads.
Concurrent collections usually provide better scalability and safer iteration semantics.
CopyOnWriteArrayList
CopyOnWriteArrayList copies the underlying array on writes. Reads are fast and iteration is safe without fail-fast behavior.
Best for scenarios with far more reads than writes: listener lists, configuration snapshots, and event subscriber registries.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteConcurrentHashMap
ConcurrentHashMap supports concurrent reads and writes with high throughput. It’s the modern default for thread-safe map needs.
Prefer atomic methods like compute, computeIfAbsent, and merge when you need read-modify-write without external locks.
BlockingQueue (ArrayBlockingQueue, LinkedBlockingQueue)
BlockingQueue supports blocking put/take operations for producer/consumer systems.
ArrayBlockingQueue is bounded with fixed capacity. LinkedBlockingQueue can also be bounded but defaults to higher flexibility.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Immutability with List.of, Set.of, Map.of
Java 9 introduced convenient factory methods: List.of, Set.of, and Map.of. They create unmodifiable collections and reject null elements.
If you pass such a collection into code that expects to modify it, you’ll get UnsupportedOperationException. Convert explicitly when you need mutability:
List<String> fixed = List.of("a", "b");
List<String> mutable = new ArrayList<>(fixed);
mutable.add("c");
Streams and collectors (modern patterns)
Streams let you transform collections with functional-style operations. They’re great for filtering, mapping, grouping, and producing new collections.
Collect to lists/sets/maps
List<String> upper = names.stream() .map(String::toUpperCase) .toList();
Set<String> unique = names.stream() .collect(Collectors.toSet());
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →To produce a map, you need a merge strategy when duplicate keys exist:
Map<String, Integer> byId = users.stream() .collect(Collectors.toMap(User::id, User::score, (a, b) -> a));
Grouping and partitioning
Group by a key:
Map<String, List<User>> byRole = users.stream() .collect(Collectors.groupingBy(User::role));
Partition into two categories (true/false):
Map<Boolean, List<User>> partitioned = users.stream() .collect(Collectors.partitioningBy(User::isActive));
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Advanced patterns you’ll actually use
Once you understand the basics, these patterns become your day-to-day power tools.
Using compute/merge in maps
Map.compute lets you update a value atomically based on the key’s current mapping.
Map<String, Integer> counts = new HashMap<>();
counts.compute("apple", (k, v) -> (v == null) ? 1 : v + 1);
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// merge does similar for increment-like cases
counts.merge("apple", 1, Integer::sum);
Pre-sizing HashMap/HashSet
If you’ll insert tens of thousands of elements at startup, pre-sizing can reduce rehashing. This helps latency spikes and CPU usage during bulk loads.
int expected = 50_000;
int initialCapacity = (int) (expected / 0.75f) + 1;
Set<String> seen = new HashSet<>(initialCapacity);
Implementing LRU with LinkedHashMap
You can implement an LRU cache by using LinkedHashMap with access order and overriding removeEldestEntry.
class LruCache<K, V> extends LinkedHashMap<K, V> { private final int maxEntries; LruCache(int maxEntries) { super(maxEntries, 0.75f, true); // accessOrder = true this.maxEntries = maxEntries; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxEntries; }
}
LruCache<String, String> cache = new LruCache<>(1000);
This is great for single-threaded caches. For concurrent caches, consider existing cache libraries or synchronize access.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteUsing EnumMap and EnumSet
If your keys come from an enum, prefer EnumMap and EnumSet. They’re more memory-efficient and faster than general-purpose map/set implementations.
enum Day { MON, TUE, WED, THU, FRI }
EnumSet<Day> weekdays = EnumSet.range(Day.MON, Day.FRI);
EnumMap<Day, String> hours = new EnumMap<>(Day.class);
hours.put(Day.MON, "09-17");
Testing and debugging collections
When collections fail, it’s rarely obvious why. A few targeted tests reduce the time you spend guessing.
Verify ordering assumptions
If code depends on iteration order, assert on iteration results using the exact collection type that guarantees order: LinkedHashMap/LinkedHashSet, or sorted collections like TreeMap.
Detect logic errors with invariants
Examples of invariants: sizes should match expectations, keys should be unique, and sets should have no duplicates by definition.
In tests, build scenarios with boundary conditions (empty, single element, large inputs, duplicates).
Reproduce hash/equality bugs deterministically
If lookups fail for custom keys, check that equals and hashCode are based on the same fields and that those fields aren’t mutated after insertion.
Also validate comparator consistency for TreeSet/TreeMap, since comparator return values govern uniqueness.
Common mistakes and how to fix them
- Modifying a collection while iterating: use
removeIf, iteratorremove(), or build a filtered copy. - Using the wrong abstraction: declare variables as interfaces (
List/Set/Map) to reduce coupling and make refactors easier. - Breaking hash-based collections: implement
equals/hashCodecorrectly and keep hash-related fields immutable. - Assuming
HashMapordering: if order matters, useLinkedHashMapor a sorted map. - Null surprises: some collections allow null keys/values (
HashMapallows one null key), whileList.of/Set.of/Map.ofreject null. - Comparator that doesn’t define a strict total order: ensure your comparator is consistent and transitive, especially for tree-based sets/maps.
Troubleshooting checklist
If something goes wrong, work through this in order. It covers the most frequent runtime symptoms.
ConcurrentModificationException
Confirm you’re not removing/adding elements directly while using enhanced for-loops or iterators without iterator.remove().
- Replace loop mutation with
removeIffor collections that support it. - For maps, remove via
Iterator<Entry<K,V>>and callit.remove(). - If concurrency is involved, switch to concurrent collections like
ConcurrentHashMapor use proper synchronization.
Map lookups always return null
This is nearly always a key equality/hash problem or a mutated key.
- Check the key type’s
equals/hashCodeimplementation. - Verify you’re using the exact same key values (not different instances with broken equality).
- If the key contains mutable fields, make those fields immutable or remove the object before mutating.
TreeSet/TreeMap drops “duplicates”
If comparator returns 0 for two different objects, they’re treated as the same key/value in tree-based structures.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
- Update the comparator to include all fields that must distinguish elements.
- Ensure comparator consistency with
equalsif that’s required by your domain logic.
Unexpected iteration order
If you’re iterating a HashMap or HashSet, don’t rely on order. The iteration order is implementation- and history-dependent.
- Switch to
LinkedHashMap/LinkedHashSetfor insertion/access order. - Switch to
TreeMap/TreeSetif you need sorting by keys. - Or sort a list snapshot before iterating.
Java Collections Tutorial FAQ
What’s the best default collection choice?
For lists, start with ArrayList. For maps, start with HashMap. For sets, start with HashSet. Switch only when you have a specific requirement: order, sorting, or concurrency.
When should I use LinkedList?
Use LinkedList mainly when you need it as a Deque and you do a lot of head/tail operations. For typical lists, ArrayList is usually faster due to cache locality.
Is HashMap thread-safe?
No. If multiple threads write to a HashMap, you can get corruption and unpredictable behavior. Use ConcurrentHashMap or external synchronization.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Why does using List.of cause UnsupportedOperationException?
List.of creates an immutable collection. If your code tries to call mutating methods like add or remove, it will throw UnsupportedOperationException. Copy it into a mutable collection first.
Do I need to pre-size HashMap?
Not always. If you insert a lot of elements and you know the approximate size, pre-sizing can reduce resizing overhead. If your size is small or unpredictable, skip the optimization.
Final Thoughts
A solid Java Collections tutorial isn’t just about knowing class names—it’s about matching data structure behavior to your problem: ordering needs, uniqueness rules, equality/hash correctness, and concurrency assumptions.
If you standardize on sensible defaults (ArrayList, HashMap, HashSet) and switch deliberately based on requirements, your code will be both more reliable and easier to maintain.
Recommended Free Tools
Quick Recap
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.




