Comparable vs Comparator in Java: Sort Collections Tutorial (2026)
Last Updated: | Author: Deep Singh
Java gives you two ways to sort a collection of objects: Comparable and Comparator. They look similar — both define a single method that returns an int — but they solve different problems, and knowing when to reach for which is a fundamental skill.
In this tutorial, you'll learn the difference between them, see working examples of both, and discover how anonymous classes and lambda expressions can make your sorting code dramatically shorter.
Comparable vs Comparator: The Difference
Here's the core comparison:
- Comparable — lives in
java.lang. Has one method:int compareTo(T other)with a single parameter. Defines the natural ordering of a class. - Comparator — lives in
java.util. Has one method:int compare(T o1, T o2)with two parameters. Defines an external, custom ordering.
The practical difference:
- Use Comparable when there's one obvious, fixed way to sort the objects. A
Studentmight always be sorted by roll number. AContactmight always be sorted alphabetically. That's the natural order — put it in the class itself. - Use Comparator when you need different sort orders at different times. A shopping site might let users sort by price, then by rating, then by name — all on the same
Productclass. That's dynamic — implement it externally.
A useful rule of thumb: Comparable = one way, defined once. Comparator = many ways, defined on demand.
Comparable Example
Suppose we have a Student class that we always want to sort by name. We implement Comparable<Student> and override compareTo.
Student.java
public class Student implements Comparable<Student> {
private int roll;
private String name;
private float marks;
private String address;
public int getRoll() { return roll; }
public void setRoll(int roll) { this.roll = roll; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getMarks() { return marks; }
public void setMarks(float marks) { this.marks = marks; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
@Override
public int compareTo(Student o) {
return this.name.compareToIgnoreCase(o.getName());
}
}
Notice that compareTo takes a single parameter (the other student) and uses the current instance as the first operand. It returns a negative number if this comes before o, zero if they're equal, and a positive number if this comes after.
Important: Use compareToIgnoreCase for a case-insensitive sort. If you use plain compareTo, uppercase letters sort before lowercase, which usually isn't what you want.
First.java — Reading from the Database and Sorting
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
public class First {
public static void main(String[] args) {
ArrayList<Student> students = new ArrayList<>();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/college?useSSL=false",
"root",
"root"
);
PreparedStatement ps = con.prepareStatement("SELECT * FROM student");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Student s = new Student();
s.setRoll(rs.getInt(1));
s.setName(rs.getString(2));
s.setMarks(rs.getFloat(3));
s.setAddress(rs.getString(4));
students.add(s);
}
rs.close();
ps.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
// Sort using the natural ordering defined by Comparable
Collections.sort(students);
Iterator<Student> it = students.iterator();
while (it.hasNext()) {
Student std = it.next();
System.out.println(
std.getRoll() + "\t" +
std.getName() + "\t" +
std.getMarks() + "\t" +
std.getAddress()
);
}
}
}
Fixes to note:
- Upgraded the MySQL driver class from
com.mysql.jdbc.Driver(removed in Connector/J 8) tocom.mysql.cj.jdbc.Driver. - Added explicit generics to the
ArrayList(new ArrayList<>()instead ofnew ArrayList()). - Closed the
ResultSet,Statement, andConnectionin the correct order. - Used
e.printStackTrace()instead ofSystem.out.println(e)— it gives the full stack trace, not just the exception message. - Added
useSSL=falseto the JDBC URL to avoid the SSL warning on modern MySQL.
Database Table
mysql> SELECT * FROM student;
+------+--------------+-------+---------+
| roll | name | marks | address |
+------+--------------+-------+---------+
| 1 | deep singh | 67.7 | gzb |
| 2 | somesh singh | 79.7 | gzb |
| 3 | salim khan | 68.7 | delhi |
+------+--------------+-------+---------+
Output (Sorted by Name)
1 deep singh 67.7 gzb
3 salim khan 68.7 delhi
2 somesh singh 79.7 gzb
The rows are now ordered alphabetically: deep, salim, somesh. The original insertion order was 1, 2, 3 — the sort reorders the in-memory list.
Comparator Example
Now suppose you want to sort the same Student objects without modifying the class. Maybe you don't own the class, or you need multiple sort orders. That's what Comparator is for.
Student.java — No Interface
public class Student {
private int roll;
private String name;
private float marks;
private String address;
public int getRoll() { return roll; }
public void setRoll(int roll) { this.roll = roll; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getMarks() { return marks; }
public void setMarks(float marks) { this.marks = marks; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
}
NameBasedSorting.java — The Comparator
import java.util.Comparator;
public class NameBasedSorting implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
}
The Comparator lives outside the Student class, so you can have as many as you need — NameBasedSorting, MarksBasedSorting, RollBasedSorting, and so on.
First.java — Using the Comparator
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
public class First {
public static void main(String[] args) {
ArrayList<Student> students = new ArrayList<>();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/college?useSSL=false",
"root",
"root"
);
PreparedStatement ps = con.prepareStatement("SELECT * FROM student");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Student s = new Student();
s.setRoll(rs.getInt(1));
s.setName(rs.getString(2));
s.setMarks(rs.getFloat(3));
s.setAddress(rs.getString(4));
students.add(s);
}
rs.close();
ps.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
// Sort using an external comparator
Collections.sort(students, new NameBasedSorting());
Iterator<Student> it = students.iterator();
while (it.hasNext()) {
Student std = it.next();
System.out.println(
std.getRoll() + "\t" +
std.getName() + "\t" +
std.getMarks() + "\t" +
std.getAddress()
);
}
}
}
Output
1 deep singh 67.7 gzb
3 salim khan 68.7 delhi
2 somesh singh 79.7 gzb
Same result as the Comparable example — but this time, the sorting logic lives outside the entity class. If you later want to sort by marks instead, you just create a new comparator and pass it in.
Shortcut 1: Anonymous Inner Class
If you only need the comparator in one place, you don't have to create a separate file for it. Use an anonymous inner class — a class defined inline at the point of use.
// Sort using an inline comparator
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
});
This is functionally identical to creating a NameBasedSorting class and passing an instance of it. The anonymous class just avoids the extra file for one-off cases.
Since Java 9, you can use the diamond operator in anonymous classes too: new Comparator<>() { ... }. It's cleaner, though not strictly necessary.
Shortcut 2: Lambda Expression
Since Comparator is a functional interface — it has exactly one abstract method — you can replace the anonymous class with a lambda expression. This is the modern way to do it, and it's dramatically shorter.
// Sort using a lambda expression
Collections.sort(students, (x, y) ->
x.getName().compareToIgnoreCase(y.getName())
);
Or, even cleaner, using the List.sort method that was added in Java 8:
students.sort((x, y) ->
x.getName().compareToIgnoreCase(y.getName())
);
Even Better: Comparator.comparing
Java 8 also added a static helper that makes common cases even more concise:
students.sort(Comparator.comparing(Student::getName));
To reverse the order:
students.sort(Comparator.comparing(Student::getName).reversed());
To sort by multiple fields (marks first, then name):
students.sort(
Comparator.comparing(Student::getMarks)
.thenComparing(Student::getName)
);
These helpers are what you'll use most often in modern Java code. The explicit compare implementation is still worth knowing for the cases where you need custom logic, but for straightforward field-based sorting, Comparator.comparing is the cleanest option.
Which One Should You Use?
A quick decision guide:
- Use Comparable when there's one obvious sort order for the class, and you're comfortable putting that logic inside the class itself. This is common for domain entities with a natural key (like an ID or a name).
- Use Comparator when you need multiple sort orders, when you don't own the class you're sorting, or when you want to keep sorting logic out of the entity.
- Use
Comparator.comparingfor simple field-based sorting — it's the most readable option in modern Java. - Use a lambda with
List.sort()when you need custom logic but don't want a separate class.
In real applications, Comparator tends to win more often than Comparable because sorting requirements change. But Comparable is the right call for entities with a truly fixed natural ordering.
Common Pitfalls
- Using
compareToinstead ofcompareToIgnoreCase. PlaincompareTois case-sensitive — all uppercase names sort before all lowercase ones. For human-readable sorting, usecompareToIgnoreCase. - Forgetting the contract of
compareTo. It must be consistent withequals. Ifa.equals(b)is true, thena.compareTo(b)must return 0. Violating this causes strange behavior in sorted collections likeTreeSet. - Returning the wrong sign. A negative result means "this comes before". A positive result means "this comes after". Returning
o.getName().compareTo(this.name)reverses the order. - Comparing with
nullvalues. NeithercompareTonorcomparehandles nulls gracefully by default. UseComparator.nullsFirst()orComparator.nullsLast()to define where nulls should go. - Sorting a primitive field with the wrong method.
Integer.compare(a, b)is the correct way to compare two ints. Subtracting them (a - b) can overflow for large values and produce the wrong result. - Mutating the collection while sorting. Don't modify the list inside a comparator — you'll get a
ConcurrentModificationExceptionor undefined behavior. - Ignoring
ClassCastException. If you sort a rawList(no generics), the runtime has to cast each element, and mismatched types throw. Always use generics. - Using
Collections.sorton a list that's already sorted by another rule. Java doesn't detect "already sorted" — it will re-run the full comparison. If you need stability, use a stable algorithm (merge sort, whichCollections.sortuses). - Not closing JDBC resources. In the examples above, always close
ResultSet,Statement, andConnectionin that order, ideally in atry-with-resourcesblock. - Deprecated MySQL driver.
com.mysql.jdbc.Driverwas removed in Connector/J 8. Usecom.mysql.cj.jdbc.Driver.
Wrapping Up
You now know the difference between Comparable and Comparator, when to use each, and how to write the same sort in three different styles — anonymous class, lambda, and Comparator.comparing.
The most important things to remember:
- Comparable = natural ordering, defined inside the class. One sort order.
- Comparator = external ordering, defined outside. Many sort orders.
- Lambda and Comparator.comparing are the modern ways to write comparators — use them.
From here, you can explore:
- Streams with sorted():
list.stream().sorted(Comparator.comparing(Student::getName)) - TreeMap and TreeSet: sorted collections that rely on
Comparableor aComparator. - Multiple-field sorting with
thenComparing. - Reverse and null-safe comparators with
reversed(),nullsFirst(), andnullsLast().
No comments:
Post a Comment