Sort ArrayList in Java: Ascending and Descending Order

Learn to sort ArrayList in Java in ascending and descending order with List.sort(), Comparator.reverseOrder(), Collections.sort() and streams.

An ArrayList of scores sorted in place in ascending and descending order and copied with a sorted stream

To sort ArrayList elements in Java, we call list.sort(Comparator.naturalOrder()) for ascending order and list.sort(Comparator.reverseOrder()) for descending order. Both calls sort the list in place, so the original ArrayList changes order and no new list is created.

We sort lists all the time in real apps, for example to show a leaderboard with the highest score first, to print names alphabetically, or to process orders from the oldest to the newest. When we need a sorted copy and want to keep the original order, we use a stream instead.

The following example sorts a list of scores both ways and creates a sorted copy with a stream.

List<Integer> scores = new ArrayList<>(List.of(75, 42, 98, 61));
scores.sort(Comparator.naturalOrder());                    // scores = [42, 61, 75, 98]
scores.sort(Comparator.reverseOrder());                    // scores = [98, 75, 61, 42]
List<Integer> copy = scores.stream().sorted().toList();    // [42, 61, 75, 98]

Notice that the stream leaves scores in descending order and returns a new list. We go through the three sorting methods, sort records by a field, combine several sort keys, handle null values and look at the mistakes that produce a list in the wrong order.

1. Different Ways to Sort an ArrayList

An ArrayList keeps its elements in insertion order, and it never sorts them on its own. Java gives us three ways to put the elements in order, and the difference between them is whether the original list changes.

An ArrayList of scores sorted in place in ascending and descending order and copied with a sorted stream
The sort() method reorders the same list, while stream().sorted() leaves the list as it is and returns a new one
Sorting methodChanges the original listWhen to use
list.sort(comparator)YesDefault choice for sorting an ArrayList in place
Collections.sort(list)YesOlder code; it calls list.sort() internally
list.stream().sorted().toList()NoWe need a sorted copy, or we also filter or limit the elements

All three methods sort in natural order (ascending for numbers, alphabetical for strings) when we pass no Comparator. For any other order, we pass a Comparator, such as Comparator.reverseOrder() for descending order.

2. Natural Order with the Comparable Interface

Numbers, strings and dates already know their natural order because they implement the Comparable interface. Our own classes have no natural order until we implement Comparable and write compareTo(). Calling sort(null) on a list of objects without a natural order throws ClassCastException.

A music app models its songs as a record. The natural order of a song is its play count, from the fewest plays to the most.

record Song(String title, int plays) implements Comparable<Song> {
    @Override
    public int compareTo(Song other) {
        return Integer.compare(this.plays, other.plays);
    }
}

The method compareTo() returns a negative number when this song comes first, zero when both are equal and a positive number when the other song comes first. We use Integer.compare() instead of subtracting the two values, because subtraction overflows for large numbers of opposite signs.

For any other order, we create a Comparator instead of changing compareTo(). A Comparator also sorts classes we cannot edit, such as classes from a library.

Comparator<Song> byTitle = Comparator.comparing(Song::title);
Comparator<Song> byPlaysDesc = Comparator.comparingInt(Song::plays).reversed();

3. Sort ArrayList in Ascending Order

The method List.sort() has been part of the List interface since Java 8, and ArrayList overrides it to sort its internal array. We pass Comparator.naturalOrder() or null, and both mean natural order.

List<Song> songs = new ArrayList<>(List.of(
        new Song("Blue", 320), new Song("Echo", 150), new Song("Atlas", 480)));
songs.sort(Comparator.naturalOrder());
List<String> byPlays = songs.stream().map(Song::title).toList();    // [Echo, Blue, Atlas]
songs.sort(Comparator.comparing(Song::title));
List<String> byName = songs.stream().map(Song::title).toList();     // [Atlas, Blue, Echo]

The first sort uses compareTo() from the record, so the song with 150 plays comes first. The second sort ignores the natural order and compares titles alphabetically.

4. Sort ArrayList in Descending Order

For descending order, we reverse the comparator, not the list. The method Comparator.reverseOrder() reverses the natural order, and reversed() reverses any comparator we built with Comparator.comparing(). The older Collections.reverseOrder() returns the same kind of comparator.

List<String> fruits = new ArrayList<>(List.of("banana", "apple", "cherry"));
fruits.sort(Comparator.reverseOrder());               // fruits = [cherry, banana, apple]
fruits.sort(Collections.reverseOrder());              // fruits = [cherry, banana, apple]

A music app shows a “Most played” chart, where the song with the most plays comes first. The chart needs descending order by one field of the record, so we reverse the field comparator.

List<Song> chart = new ArrayList<>(List.of(
        new Song("Blue", 320), new Song("Echo", 150), new Song("Atlas", 480)));
chart.sort(Comparator.comparingInt(Song::plays).reversed());
String top = chart.getFirst().title();                // "Atlas"
List<String> titles = chart.stream().map(Song::title).toList();    // [Atlas, Blue, Echo]

A common mistake is to call Collections.reverse() or the Java 21 method reversed() on an unsorted list. Both only flip the current order, so the result is sorted in descending order only if the list was sorted in ascending order first.

List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
List<Integer> flipped = nums.reversed();              // [2, 1, 3], not sorted
Collections.sort(nums);
Collections.reverse(nums);                            // nums = [3, 2, 1]

The method reversed() comes from the sequenced collections added in Java 21. It returns a reverse-ordered view of the same list, so later changes to nums show up in flipped as well.

5. Sorting by Multiple Fields, Nulls and Case

Real data has ties, missing values and mixed upper and lower case. The Comparator interface has a factory method for each of these cases, and we chain them into one comparator.

  • The method thenComparing() breaks ties with a second sort key.
  • The methods Comparator.nullsFirst() and Comparator.nullsLast() put null elements at the start or at the end instead of throwing NullPointerException.
  • The constant String.CASE_INSENSITIVE_ORDER sorts strings without treating upper case letters as smaller.
List<Song> playlist = new ArrayList<>(List.of(
        new Song("Blue", 300), new Song("Atlas", 300), new Song("Echo", 500)));
playlist.sort(Comparator.comparingInt(Song::plays).reversed()
        .thenComparing(Song::title));
List<String> ranked = playlist.stream().map(Song::title).toList();   // [Echo, Atlas, Blue]
List<String> names = new ArrayList<>(Arrays.asList("bob", null, "Alice", "carol"));
names.sort(Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER));     // names = [Alice, bob, carol, null]
List<String> plain = new ArrayList<>(List.of("bob", "Alice", "carol"));
plain.sort(null);                                                     // plain = [Alice, bob, carol]
List<String> upperFirst = new ArrayList<>(List.of("bob", "alice", "Carol"));
upperFirst.sort(null);                                                // upperFirst = [Carol, alice, bob]

The last sort shows why case matters. Natural order compares character codes, and every upper case letter has a smaller code than any lower case letter, so “Carol” lands before “alice”. We cover more combinations in sorting with multiple comparators and sorting with null values.

6. Sort ArrayList using Collections.sort()

The method Collections.sort() works the same way as List.sort(). Since Java 8, it calls list.sort() internally, so it adds nothing except a longer name, and modern code calls list.sort().

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}
List<Integer> ages = new ArrayList<>(List.of(37, 25, 41));
Collections.sort(ages);                                    // ages = [25, 37, 41]
Collections.sort(ages, Comparator.reverseOrder());         // ages = [41, 37, 25]

Both methods use a stable sort, so elements that compare as equal keep their relative order. For example, if we sort songs by title first and by plays second, songs with the same play count stay in title order. Both methods also need a modifiable list, so sorting a list from List.of() throws UnsupportedOperationException.

List<Integer> fixed = List.of(3, 1, 2);
List<Integer> safe = new ArrayList<>(fixed);
safe.sort(null);                                           // safe = [1, 2, 3]

7. Sort ArrayList using Streams

A stream sorts a copy of the elements and leaves the source list untouched. We choose a stream when other code still needs the original order, or when we also want to filter or limit the sorted elements in the same statement.

List<Integer> points = new ArrayList<>(List.of(75, 42, 98, 61));
List<Integer> asc = points.stream().sorted().toList();                              // [42, 61, 75, 98]
List<Integer> desc = points.stream().sorted(Comparator.reverseOrder()).toList();    // [98, 75, 61, 42]
List<Integer> original = points;                                                    // [75, 42, 98, 61]

The method Stream.toList() was added in Java 16 and returns an unmodifiable list. When we need to add elements to the sorted result later, we collect into an ArrayList with Collectors.toCollection(ArrayList::new).

A “Top hits” screen shows the two songs with more than 200 plays, sorted by plays in descending order.

List<Song> library = List.of(
        new Song("Blue", 320), new Song("Echo", 150), new Song("Atlas", 480), new Song("Drift", 210));
List<String> topTwo = library.stream()
        .filter(s -> s.plays() > 200)
        .sorted(Comparator.comparingInt(Song::plays).reversed())
        .limit(2)
        .map(Song::title)
        .toList();
String result = topTwo.toString();                         // "[Atlas, Blue]"

Notice that library is an unmodifiable list here. A stream never writes to its source, so sorting through a stream works even when list.sort() would throw.

8. ArrayList Sorting FAQs

Developers who sort lists for the first time often ask these questions.

8.1. Does sort() modify the original ArrayList?

Yes. Both list.sort() and Collections.sort() reorder the list in place and return void. To keep the original order, we sort a copy with new ArrayList<>(list) or use list.stream().sorted().toList().

8.2. How do we sort a list of objects by a field in descending order?

We build a comparator for the field and call reversed() on it, as in songs.sort(Comparator.comparingInt(Song::plays).reversed()). For a String field, we use Comparator.comparing(Song::title, Comparator.reverseOrder()), which reverses only that key.

8.3. Is sorting an ArrayList stable?

Yes. The List.sort() implementation in the JDK is a stable merge sort (TimSort), and Collections.sort() guarantees stability in its Javadoc. Equal elements stay in the order they had before the sort.

8.4. Why does sorting throw ClassCastException?

The elements have no natural order and we passed no comparator. Natural order needs Comparable, so we either implement Comparable in the class or pass a Comparator to sort().

8.5. What is the time complexity of ArrayList sort?

Sorting takes O(n log n) comparisons in the worst case. For a list that is already almost sorted, TimSort needs far fewer comparisons, close to O(n).

9. Conclusion

We sort an ArrayList in place with list.sort(), passing Comparator.naturalOrder() for ascending order and Comparator.reverseOrder() or a reversed field comparator for descending order. The method Collections.sort() does the same work, and the stream method sorted() returns a sorted copy instead.

For records and other classes, Comparable defines one natural order, and Comparator factory methods cover everything else, including tie-breakers, null values and case-insensitive text. Reversing an unsorted list with reversed() or Collections.reverse() does not sort it.

10. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.