By WebNest Studio Engineering Team
Java - Core Tutorial
Comparable vs Comparator
Java offers two distinct ways to define an ordering for objects: implementing the Comparable interface inside the class itself, or writing a separate Comparator. They solve related but different problems, and understanding when to use each is essential for sorting collections and building sorted data structures like TreeSet and TreeMap.
Why it matters
Sorting objects is an everyday task: employees by salary, orders by date, products by price and then by name. Java can only sort objects if it is told how to compare them, and there are two ways to do that. Knowing which to use, and how to chain comparators for several fields, covers nearly every sorting requirement you will meet.
Comparable — One Natural Ordering, Defined Inside the Class
A class implements Comparable<T> by providing a single method: compareTo(T other), which returns a negative number, zero, or a positive number depending on whether "this" object is less than, equal to, or greater than "other". This defines the class's natural ordering — the default order used by Collections.sort(), Arrays.sort(), TreeSet, and TreeMap when no other ordering is specified. A class can only have one natural ordering, because compareTo() is a single fixed method.
Comparator — Multiple External Orderings
A Comparator<T> is a separate object defining a compare(T a, T b) method, written independently of the class being compared. This lets you define as many different orderings as you need — by name, by age, by price descending — without modifying the target class at all, and is especially useful for classes you don't own (like library classes) or classes with no obviously "natural" order.
Modern Comparator Construction
Since Java 8, Comparator is a functional interface, so it is commonly built with lambdas or the static helper methods Comparator.comparing(), .thenComparing(), and .reversed(), which read almost like a sentence describing the sort order.
Comparable:compareTo(T o), one ordering, defined inside the class, used automatically by sort().Comparator:compare(T a, T b), unlimited orderings, defined externally, passed explicitly to sort().
Example
Natural ordering via Comparable, plus two external orderings via Comparator
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class Employee implements Comparable<Employee> {
String name;
int age;
Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Employee other) {
return Integer.compare(this.age, other.age); // natural order: by age
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class ComparableComparatorDemo {
public static void main(String[] args) {
List<Employee> team = new ArrayList<>(List.of(
new Employee("Ravi", 34),
new Employee("Asha", 28),
new Employee("Meera", 41)
));
java.util.Collections.sort(team); // uses Comparable (natural order: by age)
System.out.println("By age (natural): " + team);
team.sort(Comparator.comparing(e -> e.name)); // Comparator: by name
System.out.println("By name (Comparator): " + team);
team.sort(Comparator.comparingInt((Employee e) -> e.age).reversed());
System.out.println("By age descending (Comparator): " + team);
}
}
By age (natural): [Asha(28), Ravi(34), Meera(41)]
By name (Comparator): [Asha(28), Meera(41), Ravi(34)]
By age descending (Comparator): [Meera(41), Ravi(34), Asha(28)]
Common Mistakes
- Trying to implement Comparable twice with different logic — a class can define only one compareTo() method, so it has exactly one natural ordering.
- Returning inconsistent results from compareTo() or compare() (not respecting antisymmetry/transitivity), which corrupts TreeSet/TreeMap ordering silently.
- Using compareTo() subtraction tricks like "a.getAge() - b.getAge()" which can silently overflow for large or negative int values — Integer.compare() is safer.
- Forgetting that TreeSet/TreeMap use compareTo()/compare() (not equals()) to determine element uniqueness, which can cause unexpected "duplicate" rejection.
Exercise
Sort the employees by salary, highest first. Employees with the same salary should be ordered by name. Then print the names.
Expected output: Ravi, Asha, Zoya (one per line)
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class Employee {
private final String name;
private final int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
String getName() { return name; }
int getSalary() { return salary; }
}
public class SortEmployees {
public static void main(String[] args) {
List<Employee> staff = new ArrayList<>(List.of(
new Employee("Zoya", 50000),
new Employee("Ravi", 70000),
new Employee("Asha", 50000)));
// TODO: sort by salary descending, then by name
for (Employee e : staff) {
System.out.println(e.getName());
}
}
}
Show hints
- Start with
Comparator.comparingInt(Employee::getSalary)and callreversed()on it. - Add the second rule with
thenComparing(Employee::getName), afterreversed()so that names stay in ascending order.
Show solution
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class Employee {
private final String name;
private final int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
String getName() { return name; }
int getSalary() { return salary; }
}
public class SortEmployees {
public static void main(String[] args) {
List<Employee> staff = new ArrayList<>(List.of(
new Employee("Zoya", 50000),
new Employee("Ravi", 70000),
new Employee("Asha", 50000)));
staff.sort(Comparator.comparingInt(Employee::getSalary).reversed()
.thenComparing(Employee::getName));
for (Employee e : staff) {
System.out.println(e.getName()); // Ravi, Asha, Zoya
}
}
}
Quick quiz
1. Which method does a class implement to define its natural ordering?
- A. compare(a, b) from Comparator
- B. compareTo(other) from Comparable
- C. equals(other)
- D. sort()
Show answer
B. Comparable has one method, compareTo, written inside the class being compared.
2. What should a.compareTo(b) return when a comes before b?
- A. A positive number
- B. Zero
- C. A negative number
- D. true
Show answer
C. Negative means a is less than b, zero means they are equal in order, positive means a is greater.
3. You need to sort the same list of products by price on one screen and by name on another. What should you use?
- A. Two Comparable implementations in the Product class
- B. It is not possible
- C. A TreeSet
- D. One Comparator for each ordering
Show answer
D. A class can have only one compareTo, but any number of comparators can be written for it.
Interview questions
What is the difference between Comparable and Comparator?
Show answer
Comparable is implemented by the class itself through compareTo() and defines its single natural ordering; String and Integer are examples. Comparator is a separate object with a compare(a, b) method, so you can define many orderings for one class and sort classes whose source you cannot change. Sorting methods and sorted collections use the natural ordering unless a comparator is passed.
Why is "return this.age - other.age" a risky way to write compareTo?
Show answer
Subtracting two int values can overflow when they are far apart, for example a large positive number minus a large negative number, and the result then has the wrong sign. Integer.compare(this.age, other.age) always returns the correct sign and should be used instead.
What does it mean for compareTo to be consistent with equals?
Show answer
It means a.compareTo(b) == 0 exactly when a.equals(b) is true. Sorted collections such as TreeSet and TreeMap use the comparison, not equals(), to decide whether two elements are the same. If the two disagree, a TreeSet can drop an element that a HashSet would keep.
Key Points to Remember
- Comparable defines a single natural ordering inside the class via compareTo(); used automatically by sort() and by TreeSet/TreeMap.
- Comparator defines an ordering externally via compare(), and any number of them can exist for the same class.
- Comparator.comparing().thenComparing().reversed() makes multi-field, readable sort logic easy to build with lambdas.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering