Let's get started with a Microservice Architecture with Spring Cloud:
Resolving Java Exception: cannot be cast to java.lang.Comparable
Last updated: February 11, 2026
1. Overview
Java, in general, is pretty protective of types, APIs, and the operations we can perform on objects. However, in some cases, we can still shoot ourselves in the foot and encounter unexpected runtime errors.\
In this tutorial, we discuss the ClassCastException we might encounter when working with Comparable objects, as the issue can be hard to debug. We’ll also consider both sorted and unsorted collections.
2. Null Comparator
Most often, in code, we use simple collections, such as ArrayList and LinkedList. They maintain order, but don’t enforce any specific ordering of the elements inside. At the same time, we can sort them if we want. The problem is that none of them require objects to be comparable, at least not explicitly. The sort() method takes a Comparator as an argument:
@Test
void givenNonComparableTasksInArrayList_whenSortedWithoutComparator_thenThrowsClassCastException() {
List<NonComparableTask> tasks = new ArrayList<>();
tasks.add(new NonComparableTask("B", 2));
tasks.add(new NonComparableTask("A", 1));
ClassCastException ex = assertThrows(ClassCastException.class, () -> tasks.sort(null));
assertEquals(ClassCastException.class, ex.getClass());
}
In this case, we have a reasonable outcome. If the code doesn’t know how to compare the objects in a List, it will try to cast the objects in the list to Comparable and call the compareTo method. That’s why we might get a ClassCastException here. It might be confusing, since we don’t have any compile-time issues (thought IDEs might flag this). However, it’s not entirely unexpected since we’ve used null as an argument.
Thus, if we use non-comparable objects, we can pass a predefined comparator, so we will be sure that the comparison won’t fail:
private static final Comparator<NonComparableTask> BY_PRIORITY_THEN_NAME =
Comparator.comparingInt(NonComparableTask::getPriority)
.thenComparing(NonComparableTask::getName);
Let’s take an object that implements Comparable:
public class SimpleTask implements Comparable<SimpleTask> {
// Fields, constructors, getters, and setters
@Override
public int compareTo(SimpleTask other) {
if (other == null) {
throw new NullPointerException("other must not be null");
}
int byPriority = Integer.compare(this.priority, other.priority);
if (byPriority != 0) {
return byPriority;
}
return this.name.compareTo(other.name);
}
// Other methods
}
In this case, we’re taking a more aggressive approach to handling nulls, but it depends on the context where the code would be used. For example, sorted collections often disallow null values anyway, so this check might be redundant in some cases. Also, if it’s important to handle null values during comparison, we can use predefined Comparators. Comparator.nullsFirst() and Comparator.nullsLast() help place them at the beginning or at the end, respectively.
After implementing compareTo, we, technically, can pass null, and the elements would be cast to Comparable without any problems:
@Test
void givenComparableTasks_whenSortedWithoutComparator_thenNaturalOrderIsUsed() {
List<SimpleTask> tasks = new ArrayList<>();
tasks.add(new SimpleTask("Write docs", 3));
tasks.add(new SimpleTask("Fix build", 1));
tasks.add(new SimpleTask("Review PR", 2));
tasks.add(new SimpleTask("Another P1", 1));
tasks.sort(null);
assertEquals(
Arrays.asList(
new SimpleTask("Another P1", 1),
new SimpleTask("Fix build", 1),
new SimpleTask("Review PR", 2),
new SimpleTask("Write docs", 3)
),
tasks
);
}
However, this approach isn’t explicit enough. We can use the following trick to reuse the comparison logic:
@Test
void givenComparableTasks_whenSortedWithoutComparator_thenNaturalOrderIsUsedExplicitly() {
List<SimpleTask> tasks = new ArrayList<>();
tasks.add(new SimpleTask("Write docs", 3));
tasks.add(new SimpleTask("Fix build", 1));
tasks.add(new SimpleTask("Review PR", 2));
tasks.add(new SimpleTask("Another P1", 1));
tasks.sort(SimpleTask::compareTo);
assertEquals(
Arrays.asList(
new SimpleTask("Another P1", 1),
new SimpleTask("Fix build", 1),
new SimpleTask("Review PR", 2),
new SimpleTask("Write docs", 3)
),
tasks
);
}
By passing a method reference, we make it more explicit and avoid passing null, resulting in more robust code. If the implemented interfaces change, we’ll see it at compile time. Also we can use Comparator.naturalOrder() to avoid passing a null value as well. This approach could be considered more idiomatic:
@Test
void givenComparableTasks_whenSortedWithNaturalOrder_thenOrderIsCorrect() {
List<SimpleTask> tasks = new ArrayList<>();
tasks.add(new SimpleTask("Write docs", 3));
tasks.add(new SimpleTask("Fix build", 1));
tasks.add(new SimpleTask("Review PR", 2));
tasks.add(new SimpleTask("Another P1", 1));
tasks.sort(Comparator.naturalOrder());
assertEquals(
Arrays.asList(
new SimpleTask("Another P1", 1),
new SimpleTask("Fix build", 1),
new SimpleTask("Review PR", 2),
new SimpleTask("Write docs", 3)
),
tasks
);
}
However, it’s important to remember that it won’t work for non-comparable objects, since it has to fall back on real comparison logic.
3. Raw Types
Another way to force a comparison between non-comparable objects and avoid a compile-time error is to use raw types. The Collections class has a convenient method for sorting Collection objects:
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
However, it’s parameterized to accept only collections of Comparable elements. At the same time, if we have a raw collection, it’s not possible to enforce the rule:
@Test
void givenNonComparableTasksInArrayList_whenSortedWithCollectionsSort_thenThrowsClassCastException() {
ArrayList tasks = new ArrayList();
tasks.add(new NonComparableTask("B", 2));
tasks.add(new NonComparableTask("A", 1));
ClassCastException ex = assertThrows(ClassCastException.class, () -> Collections.sort(tasks));
assertEquals(ClassCastException.class, ex.getClass());
}
Raw types are a bad coding practice, and the code above is a good example of why. If we do things correctly, the code won’t even compile:
@Test
void givenNonComparableTasksInArrayList_whenSortedWithCollectionsSort_thenThrowsClassCastException() {
ArrayList<NonComparableTask> tasks = new ArrayList<>();
tasks.add(new NonComparableTask("B", 2));
tasks.add(new NonComparableTask("A", 1));
ClassCastException ex = assertThrows(ClassCastException.class, () -> Collections.sort(tasks));
assertEquals(ClassCastException.class, ex.getClass());
}
It’s better to always check the flags and notifications from IDEs or other tools to avoid such issues.
4. Ordered Collections
Another case where we might receive this exception is when we use a sorted collection, for example, a PriorityQueue or a TreeSet. This might be a less obvious case, since Java doesn’t require these collections to use Comparable objects. Its behavior is similar to that of the usual collections. We need to pass the Comparator to the constructor if we want to provide the ordering logic explicitly. If not, we have to use Comparable objects. Let’s check the case when we don’t do this:
@Test
void givenNonComparableTasks_whenUsingPriorityQueueWithoutComparator_thenAddingThrowsClassCastException() {
PriorityQueue<NonComparableTask> pq = new PriorityQueue<>();
ClassCastException ex = assertThrows(ClassCastException.class,
() -> pq.add(new NonComparableTask("First", 1)));
assertEquals(ClassCastException.class, ex.getClass());
}
@Test
void givenNonComparableTasks_whenUsingTreeSetWithoutComparator_thenAddingThrowsClassCastException() {
TreeSet<NonComparableTask> set = new TreeSet<>();
ClassCastException ex = assertThrows(ClassCastException.class,
() -> set.add(new NonComparableTask("Fix build", 1)));
assertEquals(ClassCastException.class, ex.getClass());
}
In both cases, it would be an attempt to cast the objects, which would cause the code to fail. However, on the positive side, it would fail on the first attempt at adding an element. While it would still happen at runtime, we might identify the issues slightly sooner.
To solve this problem, we have two options. The first one is to use Comparable elements:
@Test
void givenTasksInRandomOrder_whenAddedToPriorityQueue_thenPollingReturnsSortedOrder() {
PriorityQueue<SimpleTask> pq = new PriorityQueue<>();
pq.add(new SimpleTask("Write docs", 3));
pq.add(new SimpleTask("Fix build", 1));
pq.add(new SimpleTask("Review PR", 2));
pq.add(new SimpleTask("Another P1", 1));
List<SimpleTask> polled = new ArrayList<>();
while (!pq.isEmpty()) {
polled.add(pq.poll());
}
assertEquals(
Arrays.asList(
new SimpleTask("Another P1", 1),
new SimpleTask("Fix build", 1),
new SimpleTask("Review PR", 2),
new SimpleTask("Write docs", 3)
),
polled
);
}
The second approach, which might be more flexible, is to provide the Comparator while creating a sorted collection:
@Test
void givenNonComparableTasks_whenUsingTreeSetWithComparator_thenIterationIsSorted() {
Comparator<NonComparableTask> byPriorityThenName = Comparator.comparingInt(NonComparableTask::getPriority)
.thenComparing(NonComparableTask::getName);
TreeSet<NonComparableTask> set = new TreeSet<>(byPriorityThenName);
set.add(new NonComparableTask("Write docs", 3));
set.add(new NonComparableTask("Fix build", 1));
set.add(new NonComparableTask("Review PR", 2));
assertEquals(
Arrays.asList(
new NonComparableTask("Fix build", 1),
new NonComparableTask("Review PR", 2),
new NonComparableTask("Write docs", 3)
),
new ArrayList<>(set)
);
}
In general, the solutions are pretty similar to the approaches we had previously. However, the main problem is to keep these things in mind, since the IDE and compiler often won’t flag this issue.
5. Conclusion
In this article, we’ve discussed ClassCastException and its connection with Comparable objects. Java is pretty good for preventing us from using incorrect classes and ensuring that we follow the defined contracts. However, it’s still possible to get runtime exceptions because of incorrect casting. In most cases, these problems are caused by introducing code smells and not following best practices. Thus, the best approach is to configure bug reporting and linters so we don’t miss any potential issues.
We also need to follow the best practices and avoid any coding methods that would relax the type safety. Additionally, we should understand that to sort the objects, we need to identify the sorting logic.
As usual, all the code from this tutorial is available over on GitHub.

















