Skip to content
Java collections 5 min read

Comparable

When you need your custom objects to be sortable — whether by Collections.sort(), Arrays.sort(), or a sorted collection like TreeSet — Java gives you the Comparable interface. Implement it once in your class, and every sorting utility in the standard library knows how to order your objects.

What is Comparable?

Comparable<T> is a generic interface in java.lang (so no import needed). It declares exactly one method:

public int compareTo(T other);

When your class implements Comparable, you are defining its natural ordering — the “default” way instances of that class should be ranked. String, Integer, Double, LocalDate, and most other built-in types already implement it.

The compareTo Contract

Your compareTo method must return:

Return valueMeaning
negative integerthis comes before other
zerothis and other are equal in order
positive integerthis comes after other

Note: The exact magnitude doesn’t matter — only the sign. Returning -1, 0, 1 is perfectly fine, and so is returning the raw difference of two integers (with a caveat explained below).

The contract also requires consistency:

  • Antisymmetry: if a.compareTo(b) > 0 then b.compareTo(a) < 0.
  • Transitivity: if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) > 0.
  • Consistency with equals (strongly recommended): a.compareTo(b) == 0 should mean a.equals(b) is true. Violating this causes surprises inside TreeSet and TreeMap.

Your First Comparable Class

Suppose you are building an app that tracks students by GPA. You want to sort them from lowest to highest GPA.

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

public class Student implements Comparable<Student> {
    private String name;
    private double gpa;

    public Student(String name, double gpa) {
        this.name = name;
        this.gpa = gpa;
    }

    @Override
    public int compareTo(Student other) {
        // Double.compare handles NaN and avoids subtraction pitfalls
        return Double.compare(this.gpa, other.gpa);
    }

    @Override
    public String toString() {
        return name + " (" + gpa + ")";
    }

    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 3.8));
        students.add(new Student("Bob",   2.9));
        students.add(new Student("Carol", 3.5));

        Collections.sort(students);
        System.out.println(students);
    }
}

Output:

[Bob (2.9), Carol (3.5), Alice (3.8)]

Collections.sort() calls compareTo internally, producing an ascending sort with no extra configuration.

Sorting Strings and Numbers (Built-in Comparable)

You rarely need to think about Comparable for String or the numeric wrapper types — they already have a natural order:

import java.util.Arrays;

public class BuiltInSort {
    public static void main(String[] args) {
        String[] words = {"banana", "apple", "cherry"};
        Arrays.sort(words);                  // uses String.compareTo
        System.out.println(Arrays.toString(words));

        Integer[] nums = {5, 2, 8, 1};
        Arrays.sort(nums);                   // uses Integer.compareTo
        System.out.println(Arrays.toString(nums));
    }
}

Output:

[apple, banana, cherry]
[1, 2, 5, 8]

Multi-Field Comparison

Real objects often need sorting on more than one field. Sort by last name, then by first name as a tiebreaker:

public class Person implements Comparable<Person> {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName  = lastName;
    }

    @Override
    public int compareTo(Person other) {
        int cmp = this.lastName.compareTo(other.lastName);
        if (cmp != 0) return cmp;             // primary key
        return this.firstName.compareTo(other.firstName); // tiebreaker
    }

    @Override
    public String toString() {
        return lastName + ", " + firstName;
    }
}

Tip: Java 8+ offers Comparator.comparing(...).thenComparing(...) for building multi-field sorts without implementing Comparable. Use Comparable for the natural order and Comparator for ad-hoc or alternative orderings. See Comparator for details.

Comparable with TreeSet

A TreeSet keeps elements in sorted order automatically — but only if they implement Comparable (or you supply a Comparator).

import java.util.TreeSet;

public class SortedStudents {
    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<>();
        set.add(new Student("Alice", 3.8));
        set.add(new Student("Bob",   2.9));
        set.add(new Student("Carol", 3.5));

        for (Student s : set) {
            System.out.println(s);
        }
    }
}

Output:

Bob (2.9)
Carol (3.5)
Alice (3.8)

Warning: If two objects return 0 from compareTo, a TreeSet treats them as duplicates and only keeps one — even if equals() says they are different. Always keep compareTo consistent with equals.

Reversing the Natural Order

Need descending order? Call Collections.reverseOrder() — it wraps any Comparable in a reversed Comparator:

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

public class ReverseSort {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9));
        Collections.sort(nums, Collections.reverseOrder());
        System.out.println(nums);
    }
}

Output:

[9, 5, 4, 3, 1, 1]

Under the Hood

How Collections.sort Uses Comparable

Collections.sort() delegates to Arrays.sort() on the backing array. For object arrays, Java uses TimSort — a hybrid of merge sort and insertion sort that runs in O(n log n) worst-case and is O(n) on already-sorted data. Each comparison step invokes compareTo through the Comparable interface, which the JIT compiler can usually inline after the call site warms up.

The Integer Subtraction Trap

A common shortcut you might see is returning this.value - other.value instead of Integer.compare(this.value, other.value). This looks fine but overflows for large negative numbers:

// DANGEROUS — can overflow when values are far apart
return this.value - other.value;

// SAFE — use the static compare helper
return Integer.compare(this.value, other.value);

Always use Integer.compare, Double.compare, Long.compare, etc. They are branch-free and overflow-safe.

Raw vs Generic Comparable

Before Java 5, Comparable was a raw type and compareTo accepted Object. Generics changed the signature to compareTo(T other), giving you compile-time type safety. The compiler inserts a checkcast bytecode instruction at the call site, so mismatched types throw ClassCastException at runtime if you use the raw type — another reason to always use the generic form.

Impact on Sorted Structures

TreeMap and TreeSet are backed by a red-black tree. They call compareTo on every insertion and lookup, so O(log n) performance depends entirely on a fast and correct compareTo implementation. Keep compareTo consistent, side-effect-free, and inexpensive.

Quick Reference

ScenarioWhat to use
One fixed natural orderImplement Comparable<T>
Multiple orderings / external sortUse Comparator
Sorted collection, no extra configTreeSet / TreeMap with Comparable
Reverse the natural orderCollections.reverseOrder()
Compare primitives safelyInteger.compare(), Double.compare(), …
Last updated June 13, 2026
Was this helpful?