W3docs

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 indices

sort 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 later

checkedList, 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) returns true if no element of a is in b. Idiomatic for "is there any overlap between these two sets?"
  • Collections.frequency(coll, target) counts occurrences — much clearer than coll.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 of n — it's a virtual list, not a backing array.
  • Collections.reverse(list) is in-place and stable. Don't roll your own with a for loop.
  • Collections.addAll(coll, "a", "b", "c") is shorter and faster than coll.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. Collections deals 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 to Collections.unmodifiableList but 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.

java— editable, runs on the server

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.sort modifies the one you passed it.
  • binarySearch returned the index of "delta" and a negative value for "zeta". The convention -result - 1 gives the insertion point that would keep the list sorted.
  • replaceAll rewrote one string everywhere it appeared; fill overwrote 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 on add, 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.

Practice

Practice
You sort a `List<String>` with `Collections.sort(list)`, then call `Collections.binarySearch(list, 'zeta')` and the result is `-4`. What does `-4` mean?
You sort a `List<String>` with `Collections.sort(list)`, then call `Collections.binarySearch(list, 'zeta')` and the result is `-4`. What does `-4` mean?
Was this page helpful?