Back to Blog
Java

Java ArrayList Sort: Collections.sort() and Comparator

java arraylist sort: Learn how to sort an ArrayList in Java using Collections.sort, Comparator, and Stream.sorted with practical examples and performance considerations.

JavaArrayListCollections.sortComparatorJava Streams
Illustration of an ArrayList being sorted with a comparator, showing ordered elements and a sort arrow.

java arraylist sort requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you need to sort an ArrayList in Java, the Collections.sort() method is the primary tool. It sorts the list in place using the natural ordering of its elements, or a Comparator you supply. This article covers the core sorting techniques, including reverse order, multi-field comparisons, and the stream-based alternative, along with the performance and edge-case behavior you should understand before choosing an approach.

Sorting with Collections.sort()

The simplest way to sort an ArrayList is to call Collections.sort() on the list. This works when the elements implement Comparable, meaning they define a natural order. Common classes like String, Integer, and Double already implement Comparable, so you can sort them directly.

import java.util.ArrayList; import java.util.Collections; ArrayList<String> names = new ArrayList<>(); names.add("Charlie"); names.add("Alice"); names.add("Bob"); Collections.sort(names); System.out.println(names); // [Alice, Bob, Charlie]

The list is modified in place; no new list is created. The natural ordering for String is lexicographic, and for numeric types it is ascending numeric order. If you try to sort a list whose elements do not implement Comparable, the compiler will not catch it, but at runtime a ClassCastException is thrown.

Custom Sorting with Comparator

When the natural order is not what you need, pass a Comparator to Collections.sort(). A Comparator defines an ordering independently of the element class. With lambdas, this is concise and readable.

Suppose you have a Person class with a name field and an age field:

class Person { String name; int age; // constructor, getters, etc. }

To sort by age ascending:

ArrayList<Person> people = new ArrayList<>(); // add people... Collections.sort(people, (p1, p2) -> Integer.compare(p1.age, p2.age));

The lambda returns a negative integer, zero, or a positive integer depending on whether p1 is less than, equal to, or greater than p2. Using Integer.compare() avoids overflow issues that can occur with subtraction.

For a single field, you can also use Comparator.comparing():

Collections.sort(people, Comparator.comparing(p -> p.name));

This is often clearer when the comparison key is a property that itself has a natural order.

Sorting in Reverse Order

To sort in descending order, use Collections.reverseOrder() as the comparator. This works for elements with a natural ordering:

Collections.sort(names, Collections.reverseOrder());

For a custom comparator, call .reversed() on it:

Collections.sort(people, Comparator.comparing(p -> p.age).reversed());

Be careful: reversed() returns a new comparator that reverses the original ordering. The original comparator remains unchanged.

Sorting Objects by Multiple Fields

Often you need to sort by one field, then break ties with another. Comparator.comparing() and thenComparing() chain comparators to achieve this.

Collections.sort(people, Comparator.comparing(Person::getLastName) .thenComparing(Person::getFirstName));

This sorts by last name first, and for people with the same last name, by first name. You can chain as many fields as needed, and each step can be reversed individually if necessary.

Using Stream.sorted() for Non-Destructive Sorting

Java 8 introduced the Stream API, which provides a sorted() method. Unlike Collections.sort(), stream.sorted() does not modify the original list; it returns a new list (or a stream that you collect).

ArrayList<Integer> numbers = new ArrayList<>(); // add numbers... List<Integer> sorted = numbers.stream() .sorted() .collect(Collectors.toList());

You can also pass a comparator to sorted():

List<Person> sortedPeople = people.stream() .sorted(Comparator.comparing(Person::getAge)) .collect(Collectors.toList());

Use the stream approach when you need to keep the original list unchanged, or when you want to chain other stream operations like filtering or mapping before sorting. If you only need to sort the list in place, Collections.sort() is simpler and avoids the overhead of creating a new collection.

Performance and Stability Considerations

The sorting algorithm used by Collections.sort() on ArrayList is a stable, adaptive, iterative mergesort (TimSort). It runs in O(n log n) time in the worst case and O(n) for partially sorted data. Stability means that equal elements retain their relative order after sorting, which matters when you sort by multiple fields in separate passes.

Stream sorted() uses the same underlying sort for ordered streams, so its time complexity is identical. However, it incurs additional memory overhead because it collects the result into a new list. For large lists, this can be significant. In-place sorting with Collections.sort() uses a small amount of extra memory (typically O(n) for the temporary array used during mergesort), but it does not create a second copy of the list.

For primitive arrays, Java uses a dual-pivot quicksort, but ArrayList stores objects, so the TimSort behavior applies. If you are sorting a very large list and memory is a concern, prefer Collections.sort() over stream().sorted().

Common Pitfalls and Edge Cases

One common mistake is sorting a list that contains null elements. The natural ordering of null is undefined, and most comparators will throw a NullPointerException. You need to handle nulls explicitly, for example by using Comparator.nullsFirst() or nullsLast():

Collections.sort(list, Comparator.nullsLast(Comparator.naturalOrder()));

Another pitfall is modifying the list while sorting. Collections.sort() does not support concurrent modification; if you try to add or remove elements from another thread, the result is undefined. The same applies to streams, which are not thread-safe unless you use a concurrent collector.

Finally, remember that Collections.sort() works on any List, but it is most efficient for ArrayList because it can access elements by index. For a LinkedList, the same method works but the performance is worse due to the lack of random access. If you are sorting a LinkedList, consider converting it to an ArrayList first.

When you need to sort an ArrayList of custom objects, always define a Comparator that explicitly states the ordering. Relying on Comparable is fine for natural order, but for domain-specific sorting, a dedicated comparator makes the code more maintainable and less error-prone.

java arraylist sort: Practical Usage and Code Examples | RYUSLOG DEV