Java Comparable vs Comparator: Sort Elements with Real-World Examples | Complete Guide

Comparable vs Comparator in Java: Sort Collections Tutorial (2026) | D.Tech Academic

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 Student might always be sorted by roll number. A Contact might 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 Product class. 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) to com.mysql.cj.jdbc.Driver.
  • Added explicit generics to the ArrayList (new ArrayList<>() instead of new ArrayList()).
  • Closed the ResultSet, Statement, and Connection in the correct order.
  • Used e.printStackTrace() instead of System.out.println(e) — it gives the full stack trace, not just the exception message.
  • Added useSSL=false to 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.comparing for 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 compareTo instead of compareToIgnoreCase. Plain compareTo is case-sensitive — all uppercase names sort before all lowercase ones. For human-readable sorting, use compareToIgnoreCase.
  • Forgetting the contract of compareTo. It must be consistent with equals. If a.equals(b) is true, then a.compareTo(b) must return 0. Violating this causes strange behavior in sorted collections like TreeSet.
  • 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 null values. Neither compareTo nor compare handles nulls gracefully by default. Use Comparator.nullsFirst() or Comparator.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 ConcurrentModificationException or undefined behavior.
  • Ignoring ClassCastException. If you sort a raw List (no generics), the runtime has to cast each element, and mismatched types throw. Always use generics.
  • Using Collections.sort on 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, which Collections.sort uses).
  • Not closing JDBC resources. In the examples above, always close ResultSet, Statement, and Connection in that order, ideally in a try-with-resources block.
  • Deprecated MySQL driver. com.mysql.jdbc.Driver was removed in Connector/J 8. Use com.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 Comparable or a Comparator.
  • Multiple-field sorting with thenComparing.
  • Reverse and null-safe comparators with reversed(), nullsFirst(), and nullsLast().

No comments: