Comparable

Su Suriya Ravichandran Updated 14 Nov 2026
6 min read ·Lesson 85 of 133

java.lang.Comparable<T> lets a class define the natural ordering of its objects: the order that "obviously" applies, such as numbers from small to large, text alphabetically, or dates chronologically. A class that implements Comparable can be sorted with Collections.sort or Arrays.sort, used as a key in TreeMap or an element of TreeSet, and passed to Collections.min and max, all without any extra configuration.

The Interface

public interface Comparable<T> {
    int compareTo(T other);
}

a.compareTo(b) returns an int whose sign is what matters:

ResultMeaning
Negativea comes before b
Zeroa and b are equal in ordering
Positivea comes after b

Only the sign is meaningful. compareTo may return -1, -7, or -1000 for "before"; code that uses it must test < 0, == 0, or > 0, never compare with -1 or 1 directly.

System.out.println("apple".compareTo("banana"));   // -1  (a before b)
System.out.println("b".compareTo("a"));            // 1
System.out.println("Zebra".compareTo("apple"));    // -7  ('Z' is 90, 'a' is 97)

Classes That Are Already Comparable

Many JDK types implement Comparable, which is why they can be sorted directly:

TypeNatural order
Integer, Long, Double, and other numeric wrappersNumeric
StringLexicographic by UTF-16 code units (uppercase before lowercase)
Character, BooleanBy value (false before true)
BigDecimal, BigIntegerNumeric
LocalDate, LocalDateTime, Instant, DurationChronological / by length
Every enumDeclaration order
UUID, Path, FileImplementation-defined

Implementing Comparable

A class implements Comparable<ItsOwnType> and supplies compareTo:

public class Student implements Comparable<Student> {
    private final String name;
    private final int rollNo;

    public Student(String name, int rollNo) {
        this.name = name;
        this.rollNo = rollNo;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.rollNo, other.rollNo);
    }

    @Override
    public String toString() { return rollNo + ":" + name; }
}
List<Student> cls = new ArrayList<>(List.of(
    new Student("Grace", 3), new Student("Ada", 1), new Student("Linus", 2)));

Collections.sort(cls);
System.out.println(cls);                       // [1:Ada, 2:Linus, 3:Grace]
System.out.println(Collections.max(cls));      // 3:Grace

TreeSet<Student> roster = new TreeSet<>(cls);  // sorted by roll number automatically

The type parameter <Student> means compareTo receives a Student, so no cast is needed inside.

Comparing fields correctly

Use the static compare methods of the wrapper classes, or the field's own compareTo:

Integer.compare(a, b)            // int fields
Long.compare(a, b)               // long fields
Double.compare(a, b)             // double fields (handles NaN and -0.0 consistently)
Boolean.compare(a, b)
this.name.compareTo(other.name)  // fields that are themselves Comparable

Avoid the subtraction shortcut:

return this.rollNo - other.rollNo;       // WRONG for large or negative values

Subtraction can overflow. With this.rollNo = Integer.MAX_VALUE and other.rollNo = -1, the difference wraps to a negative number, reporting the larger value as "before." Integer.compare never overflows.

Multiple fields

Compare the most important field first, and move to the next only when the previous ones are equal:

@Override
public int compareTo(Employee other) {
    int c = this.department.compareTo(other.department);
    if (c != 0) return c;
    c = Integer.compare(other.salary, this.salary);       // salary descending: operands swapped
    if (c != 0) return c;
    return this.name.compareTo(other.name);
}

Swapping the operands (other first) reverses that field's order. The same logic can be written more compactly with Comparator chains, covered in the next lesson.

The compareTo Contract

For sorting and sorted collections to behave correctly, compareTo must define a consistent total order:

  1. Antisymmetry: the sign of a.compareTo(b) is the opposite of b.compareTo(a).
  2. Transitivity: if a > b and b > c, then a > c.
  3. Consistency: if a.compareTo(b) == 0, then a and b compare the same way against every other object.
  4. a.compareTo(null) should throw NullPointerException.

A broken comparison can make Collections.sort throw IllegalArgumentException: Comparison method violates its general contract, or silently produce a wrong order.

Consistency with equals

It is strongly recommended, though not required, that compareTo returns 0 exactly when equals returns true. Sorted collections (TreeSet, TreeMap) use compareTo to detect duplicates, while hash collections use equals. When the two disagree, the collections disagree too. BigDecimal is the well-known example:

BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("2.00");

System.out.println(a.equals(b));                          // false: different scale
System.out.println(a.compareTo(b));                       // 0: numerically equal
System.out.println(new HashSet<>(List.of(a, b)).size());  // 2
System.out.println(new TreeSet<>(List.of(a, b)).size());  // 1

The same two values form a set of size 2 or size 1 depending on the set implementation. When you implement both methods yourself, base them on the same fields so that this cannot happen.

Comparable and Records

Records do not implement Comparable automatically, but they can:

public record Money(long paise) implements Comparable<Money> {
    @Override
    public int compareTo(Money other) {
        return Long.compare(paise, other.paise);
    }
}

The record's generated equals compares paise too, so the natural ordering is consistent with equals.

Comparable Versus Comparator

ComparableComparator
Packagejava.langjava.util
MethodcompareTo(T other)compare(T a, T b)
DefinedInside the class being comparedOutside, as a separate object
How many orderingsOne: the natural orderAs many as you need
Used by defaultCollections.sort(list), new TreeSet<>()When passed explicitly: list.sort(cmp), new TreeSet<>(cmp)
When to useThere is one obvious orderAlternative orders, or classes you cannot modify

Give a class a natural ordering only when one ordering is clearly the right default: version numbers, money amounts, dates. A Product has no single obvious order (by name? price? rating?), so it is better sorted with comparators supplied where needed.

Practical Example: Semantic Version Numbers

Software versions like 1.10.0 and 1.9.12 must be compared part by part as numbers. Sorting them as strings gives the wrong answer, because character by character "1.10.0" comes before "1.9.12".

import java.util.*;

public final class Version implements Comparable<Version> {
    private final int major, minor, patch;

    public Version(int major, int minor, int patch) {
        if (major < 0 || minor < 0 || patch < 0) throw new IllegalArgumentException("negative part");
        this.major = major;
        this.minor = minor;
        this.patch = patch;
    }

    public static Version parse(String text) {
        String[] p = text.strip().split("\\.");
        if (p.length != 3) throw new IllegalArgumentException("expected MAJOR.MINOR.PATCH: " + text);
        return new Version(Integer.parseInt(p[0]), Integer.parseInt(p[1]), Integer.parseInt(p[2]));
    }

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

    @Override
    public boolean equals(Object o) {
        return o instanceof Version v && compareTo(v) == 0;
    }

    @Override
    public int hashCode() { return Objects.hash(major, minor, patch); }

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

    public static void main(String[] args) {
        List<String> tags = List.of("1.10.0", "1.2.3", "2.0.0", "1.9.12", "1.2.10");

        List<String> asText = new ArrayList<>(tags);
        Collections.sort(asText);
        System.out.println("String order:  " + asText);

        List<Version> versions = new ArrayList<>();
        for (String t : tags) versions.add(Version.parse(t));
        Collections.sort(versions);
        System.out.println("Version order: " + versions);

        Version installed = Version.parse("1.9.12");
        Version latest = Collections.max(versions);
        System.out.println("Update available: " + (installed.compareTo(latest) < 0) + " (latest " + latest + ")");

        TreeSet<Version> released = new TreeSet<>(versions);
        System.out.println("Next after 1.2.10: " + released.higher(Version.parse("1.2.10")));
    }
}

Output:

String order:  [1.10.0, 1.2.10, 1.2.3, 1.9.12, 2.0.0]
Version order: [1.2.3, 1.2.10, 1.9.12, 1.10.0, 2.0.0]
Update available: true (latest 2.0.0)
Next after 1.2.10: 1.9.12

The string sort compares characters, so "1.10.0" lands first and "1.2.10" before "1.2.3". Version.compareTo compares major, then minor, then patch numerically, stopping at the first difference, which produces the correct order. Because Version is Comparable, Collections.sort, Collections.max, and TreeSet all work with no further code, and higher finds the next release. equals is defined in terms of compareTo and hashCode uses the same three fields, so Version behaves consistently in both hash-based and sorted collections. The class is final with final fields, so its ordering can never change after an object is placed in a sorted collection.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Comparable and get answers drawn from it.

Signed-in readers only.