Java List Sort: Using Comparator and Comparable
java list sort: Learn how to sort a Java List using Collections.sort, List.sort, Comparator, and Comparable, including reverse order and null handling.
Sorting a list is a frequent task in Java. The java list sort operation can be performed using Collections.sort, the default List.sort method, or stream pipelines. Each approach has specific behavior regarding mutability, ordering, and performance.
The Basic Sort: Collections.sort and List.sort
The classic way to sort a List is through Collections.sort. This method sorts the list in place, meaning it modifies the original list and returns void. It works with any List implementation that supports mutable elements, such as ArrayList or LinkedList.
List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob")); Collections.sort(names); System.out.println(names); // [Alice, Bob, Charlie]
Since Java 8, List itself provides a default sort method that accepts a Comparator. Passing null uses natural ordering, which requires the elements to implement Comparable.
names.sort(null); // equivalent to natural order
For clarity, you can pass Comparator.naturalOrder() explicitly. Both Collections.sort and List.sort use the same underlying sorting algorithm, so the result is identical.
Sorting with a Custom Comparator
When your elements do not have a natural order, or you need a different ordering, use a Comparator. The Comparator interface offers static factory methods that make building comparators concise and readable.
List<Employee> employees = getEmployees(); employees.sort(Comparator.comparing(Employee::getLastName));
You can chain multiple criteria using thenComparing. For example, sort by last name, then by first name when last names are equal.
employees.sort( Comparator.comparing(Employee::getLastName) .thenComparing(Employee::getFirstName) );
For primitive fields, use comparingInt, comparingDouble, or comparingLong to avoid autoboxing overhead.
employees.sort(Comparator.comparingInt(Employee::getSalary));
Sorting Objects with Comparable
If a class has a single natural ordering, implement Comparable. This is useful when the class is always sorted the same way, such as by ID or name.
public class Employee implements Comparable<Employee> { private String lastName; private String firstName; @Override public int compareTo(Employee other) { return this.lastName.compareTo(other.lastName); } }
Now Collections.sort(employees) works without an explicit comparator. The compareTo method must be consistent with equals; otherwise, sorting may behave unexpectedly in sorted collections like TreeSet.
Reverse Order and Null Handling
To sort in descending order, use Comparator.reverseOrder() or call reversed() on an existing comparator.
names.sort(Comparator.reverseOrder()); employees.sort(Comparator.comparing(Employee::getLastName).reversed());
Null elements cause a NullPointerException when a comparator tries to compare them. Use nullsFirst or nullsLast to control where nulls appear.
names.sort(Comparator.nullsFirst(Comparator.naturalOrder())); names.sort(Comparator.nullsLast(Comparator.naturalOrder()));
These methods wrap the comparator and handle nulls without throwing. They are especially useful when data comes from external sources.
Stability and Performance Characteristics
Both Collections.sort and List.sort use a stable, adaptive merge sort (TimSort) for object arrays. Stability means that equal elements retain their original relative order after sorting. This matters when sorting by multiple keys in successive passes; a stable sort preserves earlier ordering.
The algorithm runs in O(n log n) time in the worst case and uses O(n) extra space. It performs well on partially sorted data, which is why it is the default. Stream-based sorting uses the same underlying sort, so performance characteristics are similar.
Sorting with Streams
Streams provide a functional alternative that does not modify the original list. Instead, they produce a new sorted list.
List<String> sortedNames = names.stream() .sorted() .collect(Collectors.toList());
You can pass a comparator to sorted as well.
List<Employee> sortedEmployees = employees.stream() .sorted(Comparator.comparing(Employee::getLastName)) .collect(Collectors.toList());
This approach is useful when you need to keep the original list intact or when sorting is part of a larger pipeline. Note that sorted() on a stream does not affect the source collection.
Common Pitfalls and Edge Cases
Sorting an immutable list throws UnsupportedOperationException. For example, List.of returns an immutable list, so calling sort on it fails. Always create a mutable copy first.
List<String> immutable = List.of("b", "a"); // immutable.sort(null); // throws UnsupportedOperationException List<String> mutable = new ArrayList<>(immutable); mutable.sort(null);
String sorting is case-sensitive by default. To ignore case, use String.CASE_INSENSITIVE_ORDER or a custom comparator.
names.sort(String.CASE_INSENSITIVE_ORDER);
If your list contains null elements and you do not provide a null-handling comparator, sorting will throw a NullPointerException. Always decide how nulls should be treated before sorting.
Another subtle issue is modifying the list while sorting. Since sorting is in-place, any concurrent modification can lead to unpredictable behavior. Use a synchronized list or a copy if concurrent access is possible.
Finally, be aware that Comparator implementations should be consistent with equals when used in sorted collections. An inconsistent comparator can break the contract of Set and Map implementations that rely on ordering.