Skip to content
Java collections 5 min read

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: record syntax 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:

CollectionOrderingDuplicates
TreeSetNatural or custom ComparatorNo
TreeMapKeys sorted naturally or by ComparatorNo duplicate keys
PriorityQueueHeap 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 CopyOnWriteArrayList with single-threaded sorting.

Quick Reference: Which Method to Use?

ScenarioBest Choice
Sort a List in place, natural orderCollections.sort(list) or list.sort(null)
Sort a List in place, custom orderlist.sort(Comparator.comparing(...))
Sort without mutating the originalstream().sorted().collect(...)
Always-sorted set, no duplicatesTreeSet
Always-sorted map by keyTreeMap
Sort a primitive/object arrayArrays.sort(array)
Last updated June 13, 2026
Was this helpful?