Skip to content
Laviyx Learn

Try

Java course menu

Java Sorting: Comparable, Comparator, thenComparing and reversed

Learn to sort in Java: natural ordering with Comparable and compareTo, custom orders with Comparator, Comparator.comparing, thenComparing, reversed and nullsFirst, sorting lists and arrays of objects, stable sorting, and sorted collections like TreeSet and TreeMap.

Intermediate11 min readUpdated
On this page

Sorting numbers and strings is easy, because Java already knows their order. To sort your own objects, such as students by marks or products by price, you tell Java how to compare them. There are two ways: Comparable, the object's one natural order, and Comparator, any number of custom orders.

Sorting built-in types#

Java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(42, 7, 19, 3));
        Collections.sort(nums);
        System.out.println(nums);

        int[] array = {5, 2, 9, 1};
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));

        List<String> words = new ArrayList<>(List.of("banana", "Cherry", "apple"));
        words.sort(null);                              // natural order
        System.out.println(words);
        words.sort(String.CASE_INSENSITIVE_ORDER);     // ignore case
        System.out.println(words);
    }
}
Output
[3, 7, 19, 42]
[1, 2, 5, 9]
[Cherry, apple, banana]
[apple, banana, Cherry]

The natural order of strings compares character codes, and every uppercase letter comes before every lowercase one, so "Cherry" sorts first. String.CASE_INSENSITIVE_ORDER ignores case.

Comparable: the natural order#

A class defines its natural order by implementing Comparable<T> and its single method compareTo, which returns a negative number if this comes first, zero if equal, and positive if this comes after:

Java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Version implements Comparable<Version> {
    final int major;
    final int minor;

    Version(int major, int minor) {
        this.major = major;
        this.minor = minor;
    }

    @Override
    public int compareTo(Version other) {
        if (major != other.major) {
            return Integer.compare(major, other.major);
        }
        return Integer.compare(minor, other.minor);
    }

    @Override
    public String toString() {
        return major + "." + minor;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Version> versions = new ArrayList<>(List.of(
                new Version(2, 1), new Version(1, 9), new Version(2, 0), new Version(1, 10)));
        Collections.sort(versions);
        System.out.println(versions);
    }
}
Output
[1.9, 1.10, 2.0, 2.1]

Use Integer.compare(a, b) rather than a - b, which can overflow for large values. String, wrapper classes, LocalDate and enums all implement Comparable.

Comparator: any order you like#

A Comparator<T> is a separate object that compares two values. With the factory methods in Comparator, most orders take one line:

Java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

record Student(String name, int marks, int age) {}

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(List.of(
                new Student("Ravi", 85, 21),
                new Student("Asha", 92, 20),
                new Student("Meera", 85, 19),
                new Student("John", 78, 22)));

        students.sort(Comparator.comparing(Student::name));
        System.out.println(names(students));

        students.sort(Comparator.comparingInt(Student::marks).reversed());
        System.out.println(names(students));

        students.sort(Comparator.comparingInt(Student::marks).reversed()
                .thenComparing(Student::name));
        System.out.println(names(students));

        students.sort(Comparator.comparingInt(Student::age));
        System.out.println(names(students));
    }

    static List<String> names(List<Student> list) {
        List<String> out = new ArrayList<>();
        for (Student s : list) out.add(s.name());
        return out;
    }
}
Output
[Asha, John, Meera, Ravi]
[Asha, Meera, Ravi, John]
[Asha, Meera, Ravi, John]
[Meera, Asha, Ravi, John]
Building blockMeaning
Comparator.comparing(keyExtractor)Compare by a key, such as Student::name
comparingInt / comparingDoubleSame, for primitive keys (no boxing)
.reversed()Reverse the order
.thenComparing(...)Tie-breaker when the first key is equal
Comparator.naturalOrder() / reverseOrder()Natural order and its reverse
Comparator.nullsFirst(...) / nullsLast(...)Handle null values safely

Student::name is a method reference, a short form of the lambda s -> s.name(); see Lambdas.

Look at the second line: Meera and Ravi both have 85, and Meera stays before Ravi because the list was already sorted by name. Java's object sort is stable: elements that compare as equal keep their previous relative order. The third sort gives the same result, but by stating the tie-breaker with thenComparing it no longer depends on the list's previous order, which is safer.

Comparable or Comparator?#

  • Implement Comparable when a class has one obvious natural order (versions, dates, money).
  • Use a Comparator for any other order, for classes you cannot change, or when you need several orders.

Sorted collections#

TreeSet, TreeMap and PriorityQueue keep their contents ordered by the natural order, or by a Comparator passed to the constructor:

Java
import java.util.Comparator;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        TreeSet<String> byLength = new TreeSet<>(
                Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
        byLength.addAll(java.util.List.of("banana", "fig", "apple", "kiwi", "date"));
        System.out.println(byLength);
    }
}
Output
[fig, date, kiwi, apple, banana]

A sorted set treats elements that compare as 0 as duplicates. Without the thenComparing tie-breaker, "kiwi" and "date" (both length 4) would count as the same element, and one would be dropped.

Common mistakes#

Practice#

Practice exercise

Given products as records Product(String name, String category, double price): Laptop/Electronics/55000, Pen/Stationery/25, Monitor/Electronics/12000, Notebook/Stationery/60, Headphones/Electronics/2500. Sort them by category, and within a category by price from highest to lowest, and print the names.

Show solution
Java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

record Product(String name, String category, double price) {}

public class SortProducts {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>(List.of(
                new Product("Laptop", "Electronics", 55000),
                new Product("Pen", "Stationery", 25),
                new Product("Monitor", "Electronics", 12000),
                new Product("Notebook", "Stationery", 60),
                new Product("Headphones", "Electronics", 2500)));

        products.sort(Comparator.comparing(Product::category)
                .thenComparing(Comparator.comparingDouble(Product::price).reversed()));

        for (Product p : products) {
            System.out.println(p.category() + " " + p.name() + " " + p.price());
        }
    }
}
Output
Electronics Laptop 55000.0
Electronics Monitor 12000.0
Electronics Headphones 2500.0
Stationery Notebook 60.0
Stationery Pen 25.0

.reversed() is applied to the price comparator only, inside thenComparing, so the category order stays ascending.

Key takeaway#

Finished this lesson?