Sorting Collections
Sorting is one of the most common tasks in any program — ordering a list of names, ranking scores, or grouping products by price. Java gives you several clean, expressive ways to sort collections, from a single method call to flexible multi-field comparators.
Natural Ordering with Collections.sort()
The simplest way to sort a List of elements that already know their own order (numbers, strings, dates) is Collections.sort(). It sorts in place and relies on elements implementing the Comparable interface.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NaturalSort {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob", "Diana"));
Collections.sort(names);
System.out.println(names);
}
}
Output:
[Alice, Bob, Charlie, Diana]
The same works for numbers — Integer, Double, Long all implement Comparable out of the box.
List<Integer> scores = new ArrayList<>(List.of(42, 7, 99, 13));
Collections.sort(scores);
System.out.println(scores); // [7, 13, 42, 99]
Tip:
List.sort(null)is the modern alternative introduced in Java 8 — it does the same thing but reads more naturally as a method on the list itself.
Reverse Order
Pass Collections.reverseOrder() as a Comparator to flip the direction:
Collections.sort(names, Collections.reverseOrder());
System.out.println(names); // [Diana, Charlie, Bob, Alice]
Sorting with a Custom Comparator
When you need an ordering that isn’t the element’s natural order — say, sort strings by length — pass a Comparator lambda:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LengthSort {
public static void main(String[] args) {
List<String> words = new ArrayList<>(List.of("banana", "kiwi", "fig", "strawberry"));
Collections.sort(words, (a, b) -> a.length() - b.length());
System.out.println(words);
}
}
Output:
[fig, kiwi, banana, strawberry]
Tip:
Comparator.comparingInt(String::length)is cleaner and avoids the subtle integer-overflow risk of subtraction. Prefer it whenever you compare numeric fields.
words.sort(Comparator.comparingInt(String::length));
Sorting Custom Objects
Suppose you have a Product class. You can sort a list of products by price:
import java.util.*;
public class ProductSort {
record Product(String name, double price) {}
public static void main(String[] args) {
List<Product> products = new ArrayList<>(List.of(
new Product("Keyboard", 49.99),
new Product("Monitor", 299.00),
new Product("Mouse", 19.99)
));
products.sort(Comparator.comparingDouble(Product::price));
products.forEach(p -> System.out.println(p.name() + " - $" + p.price()));
}
}
Output:
Mouse - $19.99
Keyboard - $49.99
Monitor - $299.0
Note:
recordsyntax requires Java 16+. For older versions, replace with a regular class. See Records for more.
Multi-Field (Chained) Sorting
Comparator.thenComparing() lets you chain sort keys — like sorting employees by department, then by name within each department:
import java.util.*;
public class ChainedSort {
record Employee(String dept, String name) {}
public static void main(String[] args) {
List<Employee> staff = new ArrayList<>(List.of(
new Employee("Engineering", "Zara"),
new Employee("Marketing", "Alice"),
new Employee("Engineering", "Alice"),
new Employee("Marketing", "Bob")
));
staff.sort(Comparator.comparing(Employee::dept)
.thenComparing(Employee::name));
staff.forEach(e -> System.out.println(e.dept() + " | " + e.name()));
}
}
Output:
Engineering | Alice
Engineering | Zara
Marketing | Alice
Marketing | Bob
Null-Safe Sorting
Real-world data has nulls. Comparator.nullsFirst() and Comparator.nullsLast() handle them gracefully:
List<String> withNulls = new ArrayList<>(Arrays.asList("Banana", null, "Apple", null, "Cherry"));
withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println(withNulls); // [null, null, Apple, Banana, Cherry]
Sorting with the Stream API
If you don’t want to mutate the original list, use Stream.sorted() to produce a new sorted sequence:
import java.util.List;
import java.util.stream.Collectors;
public class StreamSort {
public static void main(String[] args) {
List<String> original = List.of("Charlie", "Alice", "Bob");
List<String> sorted = original.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Original: " + original);
System.out.println("Sorted: " + sorted);
}
}
Output:
Original: [Charlie, Alice, Bob]
Sorted: [Alice, Bob, Charlie]
Pass a Comparator to .sorted() for custom ordering, just like with List.sort().
Always-Sorted Collections
Sometimes you want a collection that stays sorted automatically rather than sorting on demand:
| Collection | Ordering | Duplicates |
|---|---|---|
TreeSet | Natural or custom Comparator | No |
TreeMap | Keys sorted naturally or by Comparator | No duplicate keys |
PriorityQueue | Heap order (min by default) | Yes |
import java.util.TreeSet;
TreeSet<String> sorted = new TreeSet<>();
sorted.add("Charlie");
sorted.add("Alice");
sorted.add("Bob");
System.out.println(sorted); // [Alice, Bob, Charlie]
Every insertion maintains order automatically — ideal when you frequently add elements and always need them sorted.
Arrays.sort() for Plain Arrays
For raw arrays (not collections), use Arrays.sort() from the Arrays utility class:
import java.util.Arrays;
int[] nums = {5, 2, 8, 1, 9};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums)); // [1, 2, 5, 8, 9]
For object arrays, you can again pass a Comparator:
String[] words = {"banana", "fig", "kiwi"};
Arrays.sort(words, Comparator.comparingInt(String::length));
System.out.println(Arrays.toString(words)); // [fig, kiwi, banana]
Under the Hood
Collections.sort() and List.sort() both delegate to Arrays.sort() on the backing array after converting the list to an array. The algorithm used is TimSort — a hybrid of merge sort and insertion sort that Java has used since Java 7.
Key properties of TimSort:
- Stable — equal elements keep their original relative order. This makes chained comparators reliable.
- Adaptive — it detects already-sorted or nearly-sorted runs and merges them efficiently.
- Time complexity — O(n log n) worst case, but often much faster on real-world (partially ordered) data.
- Space complexity — O(n) for the temporary merge buffer.
TreeSet and TreeMap use a red-black tree internally, giving O(log n) insertion and lookup while maintaining sorted order at all times — a better choice than repeatedly sorting a list when the data changes often.
Warning: Sorting is not thread-safe. If multiple threads share a mutable list, synchronize access or use a
CopyOnWriteArrayListwith single-threaded sorting.
Quick Reference: Which Method to Use?
| Scenario | Best Choice |
|---|---|
Sort a List in place, natural order | Collections.sort(list) or list.sort(null) |
Sort a List in place, custom order | list.sort(Comparator.comparing(...)) |
| Sort without mutating the original | stream().sorted().collect(...) |
| Always-sorted set, no duplicates | TreeSet |
| Always-sorted map by key | TreeMap |
| Sort a primitive/object array | Arrays.sort(array) |
Related Topics
- Comparable — implement natural ordering directly on your class
- Comparator — define flexible, reusable sort strategies as objects or lambdas
- Comparable vs Comparator — when to use each approach
- Collections Utility Class — full overview of
Collectionshelpers beyond sorting - Stream Operations (filter/map/reduce) — pipeline-style data processing including sorted streams
- TreeSet — a set that always keeps its elements in sorted order