Java Collections Utility Class
Use the Collections utility class in Java to sort, search, reverse, shuffle, and wrap collections.
java.util.Collections is the standard library's grab bag of static helpers that operate on collections. Think of it the way you already think of java.util.Arrays: a final class with no instance state, only static methods. You never write new Collections() — you write Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).
It's easy to confuse the class with the interface it lives next to: Collection<E> (interface, with a capital C and no s) is the supertype of List, Set, and Queue; Collections (class, plural) is the utility toolbox. The class doesn't implement the interface; it just operates on collections that do.
A guided tour of the toolbox
The methods cluster into six themes. We'll touch each, with the next two chapters drilling into sorting and searching specifically.
1. Ordering and reordering
Collections.sort(list); // natural order — requires Comparable
Collections.sort(list, comparator); // custom comparator
Collections.reverse(list); // in place
Collections.shuffle(list); // pseudo-random permutation
Collections.shuffle(list, new Random(42)); // deterministic shuffle with a seeded RNG
Collections.rotate(list, 2); // [a,b,c,d,e] → [d,e,a,b,c]
Collections.swap(list, 0, list.size() - 1); // swap two indicessort is a stable mergesort — equal elements keep their relative order. shuffle performs a Fisher-Yates shuffle, which is uniformly random when the RNG is. rotate is what you want when you mean "shift everything by N positions, wrapping around the ends." reverse, swap, and rotate mutate the list in place; none of them return anything useful.
2. Searching
int i = Collections.binarySearch(sortedList, key); // O(log n) — list must be sorted
int j = Collections.binarySearch(sortedList, key, comparator);
T max = Collections.max(coll);
T min = Collections.min(coll, comparator);
int n = Collections.frequency(coll, target); // how many times target appears
boolean disjoint = Collections.disjoint(a, b); // no element in common?binarySearch has its own chapter — the short version: the list must already be sorted in the same order the search uses, and a negative return means "not found, but you can compute the insertion point as -result - 1."
3. Filling, copying, replacing
Collections.fill(list, "x"); // overwrite every slot with "x"
Collections.copy(dest, src); // copy src into dest; dest.size() must be ≥ src.size()
Collections.replaceAll(list, "old", "new"); // returns true if anything changed
Collections.nCopies(5, "x"); // immutable list with "x" 5 times
Collections.singleton(value); // immutable Set of one
Collections.singletonList(value); // immutable List of one
Collections.singletonMap(k, v); // immutable Map of one entry
Collections.emptyList(); Collections.emptyMap(); Collections.emptySet();The empty/singleton/nCopies factories return cached, immutable instances — they don't allocate per call. They're a small, free optimisation when you need a known-empty or known-tiny collection.
4. Synchronized wrappers (mostly historical)
List<String> lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int> lockedMap = Collections.synchronizedMap(new HashMap<>());
Set<String> lockedSet = Collections.synchronizedSet(new HashSet<>());These wrap a collection so every method acquires a lock on the wrapper. The same caveat as Hashtable applies: compound operations are still racy, and iterators must be wrapped in synchronized (wrapper) { ... } blocks explicitly:
synchronized (lockedList) {
for (String s : lockedList) { ... } // safe: holds the lock for the whole walk
}In modern code reach for ConcurrentHashMap, CopyOnWriteArrayList, and ConcurrentSkipListSet instead. The synchronized wrappers exist for retrofitting a non-thread-safe API to a thread-safe one when nothing else fits.
5. Unmodifiable wrappers
List<String> frozen = Collections.unmodifiableList(mutableList);
Set<String> frozenS = Collections.unmodifiableSet(mutableSet);
Map<K, V> frozenM = Collections.unmodifiableMap(mutableMap);These wrap a collection so mutation methods throw UnsupportedOperationException. The original collection is still mutable — the wrapper is a read-only view. Changes through the original show up through the view. That's a key difference from the List.of(...) / Set.of(...) / Map.of(...) factories that produce fully immutable collections backed by their own storage. The next chapter compares the two.
6. Single-element and type-safe views
List<Object> objects = new ArrayList<>();
List<String> safe = Collections.checkedList(objects, String.class);
safe.add("ok"); // fine
((List) safe).add(42); // throws ClassCastException immediately, not latercheckedList, checkedSet, checkedMap install a runtime type-check on every insertion. Useful in legacy code that passes generic collections through Object-typed APIs — the wrapper fails loudly at the point of insertion instead of much later at the point of retrieval.
A few small but high-value methods
Collections.disjoint(a, b)returnstrueif no element ofais inb. Idiomatic for "is there any overlap between these two sets?"Collections.frequency(coll, target)counts occurrences — much clearer thancoll.stream().filter(x -> x.equals(target)).count().Collections.nCopies(n, x)is sometimes exactly what you want, e.g.result.addAll(Collections.nCopies(rows, "pad")). The list returned is immutable but consumes O(1) memory regardless ofn— it's a virtual list, not a backing array.Collections.reverse(list)is in-place and stable. Don't roll your own with aforloop.Collections.addAll(coll, "a", "b", "c")is shorter and faster thancoll.addAll(List.of("a", "b", "c"))because it avoids the intermediate list.
What Collections is not
- Not a Stream replacement. For filter/map/reduce, use streams.
Collectionsdeals in mutation and direct queries, not declarative pipelines. - Not the place for
List.of/Set.of/Map.of. Those are factories on the interfaces, added in Java 9. They live next toCollections.unmodifiableListbut they're not part of this class. - Not the place for stream collectors. That's
java.util.stream.Collectors. Different package, different role.
A worked example: the toolbox in one program
The program below applies a dozen Collections methods to a single list and a single map to make the API tangible: sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll, and the unmodifiable view.
What to take from the run:
- Every method either mutates in place (
sort,reverse,shuffle,rotate,swap,fill,replaceAll) or returns a primitive answer (min,max,frequency,disjoint,binarySearch). Nothing in the toolbox returns a "new" sorted list —Collections.sortmodifies the one you passed it. binarySearchreturned the index of"delta"and a negative value for"zeta". The convention-result - 1gives the insertion point that would keep the list sorted.replaceAllrewrote one string everywhere it appeared;filloverwrote every slot. Both work on the same list — handy when you want to recycle storage.Collections.unmodifiableList(backing)returned a read-only view. The view threw onadd, but mutating the backing list still worked, and the change showed up through the view. The view is not a copy.
What's next
The toolbox is now in your head at the index level. Two operations deserve a closer look because their detail matters: Sorting Java Collections (when to use Collections.sort vs List.sort vs stream().sorted(), stable order, comparator builders, primitive specialisations) and Searching Java Collections (contains, indexOf, binarySearch, and stream-based search). The next chapter is sorting.