java.util.Collections is a class full of static helper methods that act on the collections you already have. It doesn't define a new collection type. Instead, it gives you ready-made operations like sorting a product list, finding the cheapest item, shuffling recommendations, or grabbing a single-element list, so you don't have to write that code yourself. This lesson walks through the methods you'll actually use day to day, grouped by what they do.
A List, Set, or Map only knows how to do its own job. An ArrayList knows how to add, remove, and look up by index. It doesn't know how to sort itself in reverse order, find the minimum element, or be shuffled into a random order. Those operations are general; they work the same way for any list. So instead of repeating that logic on every collection class, the JDK packages it once into java.util.Collections as static methods that take a collection as the first argument.
The mental model is "free functions on top of collections". If you have a List<Product> and want it sorted, call Collections.sort(products). The list mutates in place. Same for shuffling, reversing, filling, and so on. For methods like min, max, and frequency, the call returns a value without modifying the collection.
Here's a map of the categories:
The diagram groups the methods by what they do to the collection: read it, mutate it, wrap it, or return a fixed value. We'll work through each group with concrete e-commerce examples.
A note on naming. The class java.util.Collections (plural, with an s) is the utility class. The interface java.util.Collection (singular) is the supertype of List, Set, and Queue. They sound almost identical, but they're different things. Throughout this lesson, Collections.sort(...) means the utility method on the plural class.
Collections.sort puts a list in order. The simplest form takes a list of elements that already know how to compare themselves, like String, Integer, or any type that implements Comparable. The sort happens in place; the list is mutated.
The list started in the order the items were added and ended in ascending price order. The sort is stable, meaning equal elements keep their original relative order, and it runs in O(n log n) time.
For anything other than the natural order (price ascending, alphabetical, smallest-first), pass a Comparator as the second argument. Here we'll just use the two built-ins from Collections itself.
Collections.reverseOrder() returns a comparator that reverses the natural ordering. Pair it with sort to get highest-first, biggest-first, or Z-to-A:
Same data, opposite order. No custom comparator needed.
Collections.sort is O(n log n). It also allocates a temporary array internally. If you're sorting a tiny list inside a hot loop, that allocation can add up. For a one-shot sort on a few thousand items, it's not worth worrying about.
Collections.binarySearch looks up an element in a sorted list in O(log n) time. The catch, and it's a big one, is that the list must already be sorted. If it isn't, the result is undefined: you might get a wrong index, a negative number that means nothing, or what looks like a correct answer purely by accident.
The list is alphabetical, and "Pen" lives at index 2. The search divides the list in half each step, so even with a million products it would finish in roughly 20 comparisons.
When the element isn't present, binarySearch returns a negative number. It's not just -1. The return value is -(insertion_point) - 1, where insertion_point is where the element would go to keep the list sorted.
"Marker" isn't in the list. If it were, it would slot in at index 1 (between "Eraser" and "Notebook"). The return value -2 is the encoded version of "would go at index 1". You rarely need the encoded form directly, but the contract matters because a -1 return doesn't mean "not found at index 0"; it means "would insert at index 0".
The "must be sorted" rule is a common pitfall. Here's what goes wrong on an unsorted list:
"Pen" is actually in the list at index 0, but binarySearch returns -1. The list isn't sorted, so the algorithm's halving logic walks in the wrong direction and never finds it. The method didn't lie; it just operates under the assumption that the input is sorted, and the assumption was broken. If the input is unsorted and you don't know it, use indexOf, which is O(n) but doesn't care about order.
binarySearch on a LinkedList is O(n log n), not O(log n), because each midpoint lookup has to walk from the head. For binary search on lists with more than a few hundred items, use ArrayList.
When you want the smallest or largest element of a collection, Collections.min and Collections.max save you the loop. They work on anything that implements Collection, including List, Set, and Queue. The elements need to be comparable to each other, just like with sort.
The list isn't sorted, and it doesn't need to be. min and max walk the collection once in O(n) and return the smallest or largest element they find. Both throw NoSuchElementException if the collection is empty, which is a reasonable signal that asking for the cheapest item in an empty cart doesn't have a sensible answer.
Collections.frequency(collection, target) counts how many times an element appears in a collection. It's handy when you want a quick tally without setting up a Map<Element, Integer>.
frequency uses equals to compare, so it works for any type whose equals method is implemented correctly. It's O(n) because it has to scan the whole collection.
Collections.disjoint(c1, c2) returns true if the two collections have no elements in common. It's the inverse of "do these overlap?" without writing a nested loop.
The two lists share no elements, so disjoint returns true. If even one element matches, the return value flips to false.
This group changes the list in place. They're useful when you want to rearrange, fill, or copy data without writing the loop yourself.
Collections.reverse flips the order. Last becomes first, first becomes last.
Reversing is O(n) and swaps elements pairwise in place. A common use is showing order history with the most recent order on top.
Collections.shuffle puts the list in a random order. Use it for shuffling product recommendations on a homepage so every visitor sees a slightly different layout.
Output (varies between runs):
The output changes every time you run it because shuffle uses a default random source. For reproducible shuffles in tests, pass a Random with a fixed seed as the second argument: Collections.shuffle(list, new Random(42)).
Collections.swap(list, i, j) swaps two elements by index. Saves the temp-variable dance.
Collections.fill(list, value) replaces every element with the given value. Use it to reset a list to a default state without recreating it.
Collections.copy(dest, src) copies elements from src into dest, position by position. The destination must already be at least as long as the source, or you get IndexOutOfBoundsException. The destination's size doesn't grow; only the existing slots are overwritten.
Collections.rotate(list, distance) rotates the elements by the given amount. Positive distance moves elements toward the end; negative moves them toward the front. The list wraps around.
Each element moved one step forward, and the last element wrapped around to the front.
Collections.addAll(collection, elements...) appends one or more elements to a collection in a single call. It accepts varargs, so you can pass them inline.
It's a shortcut for the loop you'd otherwise write. For adding the contents of another collection, the collection's own addAll(Collection) method works too; Collections.addAll is specifically the varargs form.
Sometimes you need a collection that contains exactly one element, or no elements at all, or the same element repeated n times. The factories on Collections produce these without going through new ArrayList<>() and a series of add calls.
Collections.singletonList(element) returns an immutable list with exactly one element. It's lighter than a one-element ArrayList and signals intent: this list will never have anything added to or removed from it.
The list is immutable. Calling defaultCart.add("Pen") throws UnsupportedOperationException. The related methods Collections.singleton(element) (a Set) and Collections.singletonMap(key, value) (a Map) work the same way for those types.
Collections.emptyList(), Collections.emptySet(), and Collections.emptyMap() return immutable empty collections. They're useful as default return values from methods that didn't find anything, instead of returning null.
The caller can now loop, check .size(), or pass the result on without worrying about a null check. Each emptyXxx factory returns the same shared instance every time, so there's no allocation cost.
Collections.nCopies(n, element) returns an immutable list with the same element repeated n times. Use it when you need a placeholder list of a known length.
The list looks like five separate slots, but internally it stores the element once and reports the same value for every index. That makes nCopies cheap regardless of n, but it also means the list is immutable.
Collections also offers wrappers that change the behavior of a collection without copying its data. Two families are worth flagging here:
Collections.synchronizedList(list), synchronizedSet(set), and synchronizedMap(map) return a thread-safe view that synchronizes every method on a single lock.Collections.unmodifiableList(list), unmodifiableSet(set), and unmodifiableMap(map) return a read-only view that throws UnsupportedOperationException on any modification attempt.Keep this table handy when you're reaching for a method and can't remember the exact name or shape:
| Method | What it does | Time complexity | Notes |
|---|---|---|---|
sort(list) | Sorts a list in natural order | O(n log n) | Mutates in place; stable |
sort(list, comparator) | Sorts using a custom comparator | O(n log n) | Mutates in place |
reverseOrder() | Returns a comparator for descending order | O(1) | Use with sort |
binarySearch(list, key) | Finds element index in a sorted list | O(log n) on ArrayList | Negative return means not found |
min(collection) | Returns smallest element | O(n) | Throws if empty |
max(collection) | Returns largest element | O(n) | Throws if empty |
frequency(c, target) | Counts occurrences of an element | O(n) | Uses equals |
disjoint(c1, c2) | True if no elements in common | O(n) | |
reverse(list) | Reverses element order in place | O(n) | |
shuffle(list) | Randomly permutes the list | O(n) | Pass Random for reproducibility |
swap(list, i, j) | Swaps elements at two indexes | O(1) | |
fill(list, value) | Sets every element to value | O(n) | |
copy(dest, src) | Copies src into dest position by position | O(n) | dest must be sized |
rotate(list, distance) | Rotates elements by distance | O(n) | Negative = toward front |
addAll(c, e1, e2, ...) | Appends elements via varargs | O(n) | |
singletonList(e) | Immutable list of one element | O(1) | |
emptyList(), emptySet(), emptyMap() | Immutable empty collection | O(1) | Shared instance |
nCopies(n, e) | Immutable list of n copies of e | O(1) |
The general rule is "don't reinvent these". If you find yourself writing a loop to find the maximum price, count occurrences, or flip a list end-to-end, check this table first. The utility method is almost always clearer, shorter, and at least as fast.
10 quizzes