Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
World desk13 min

Java Collections Tutorial

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

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.

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

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.

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

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.

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

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).

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

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.

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

LinkedList

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.

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

TreeSet

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.

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

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.

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

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.

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

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.

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

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).

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

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.

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

For priority-based ordering: use PriorityQueue. For LIFO: use ArrayDeque and operate from the same end.

Rank #3
SunFounder Raphael Ultimate Starter Kit for Raspberry Pi 5 4 B 3B B+ 400, Zero 2 W, RoHS Compliant, Python, C Java, Online Tutorials & Video Courses for Beginners (Raspberry PI NOT Included)
  • 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);

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

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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

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.

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

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.

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

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
Sale
Java Cookbook
  • Used Book in Good Condition

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.

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

If 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.

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

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.

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

ConcurrentHashMap

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.

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

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());

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

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.Support on Ko-Fi

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.

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

Using 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.

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

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.

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

Common mistakes and how to fix them

  • Modifying a collection while iterating: use removeIf, iterator remove(), 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/hashCode correctly and keep hash-related fields immutable.
  • Assuming HashMap ordering: if order matters, use LinkedHashMap or a sorted map.
  • Null surprises: some collections allow null keys/values (HashMap allows one null key), while List.of/Set.of/Map.of reject 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().

  1. Replace loop mutation with removeIf for collections that support it.
  2. For maps, remove via Iterator<Entry<K,V>> and call it.remove().
  3. If concurrency is involved, switch to concurrent collections like ConcurrentHashMap or use proper synchronization.

Map lookups always return null

This is nearly always a key equality/hash problem or a mutated key.

  1. Check the key type’s equals/hashCode implementation.
  2. Verify you’re using the exact same key values (not different instances with broken equality).
  3. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Update the comparator to include all fields that must distinguish elements.
  2. Ensure comparator consistency with equals if 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.

  1. Switch to LinkedHashMap/LinkedHashSet for insertion/access order.
  2. Switch to TreeMap/TreeSet if you need sorting by keys.
  3. 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.

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

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.

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

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

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

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.