Comparison method violates its general contract, error can be very painful to debug. I recently encountered this issue in one of the production servers and it was only sometimes the error was thrown. Here I would discuss some cases that can lead to such an error.
Stacktrace
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:777) at java.util.TimSort.mergeAt(TimSort.java:514) at java.util.TimSort.mergeCollapse(TimSort.java:441) at java.util.TimSort.sort(TimSort.java:245) at java.util.Arrays.sort(Arrays.java:1438) at java.util.Arrays$ArrayList.sort(Arrays.java:3895) at java.util.Collections.sort(Collections.java:175)
Possible Coding Mistakes
Example 1:
In the below example the last statement in compare
, return 0
is never executed and the compare method returns 1 even if the integers are equal.
static void sort(Integer... ints){ List<Integer> list = Arrays.asList(ints); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if(o1 < o2){ return -1; } else if (o1 <= o2){ return 1; } return 0; } }); System.out.println(list); }
Solution :
The solution to the above example is quite simple. Change the <=
to <
Example 2:
In the below example two integers being equal is completely ignored.
static void sort(Integer... ints){ List<Integer> list = Arrays.asList(ints); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if(o1 < o2){ return -1; } else { return 1; } } }); System.out.println(list); }
Solution
The solution to this issue is also simple. Add the else block for the equals also.
Example 3:
We can also have a case when a comparison is being done and something is changed by another thread at the same time. If we have a comparison on size of a list but that list is being updated at the same time by another thread. This case is quite hard to debug as it may fail only in very rare cases. In the below example I am updating the list while running the comparison on the same time so it also throws the contract violation error.
static void sort(){ final List<List<Integer>> list = new ArrayList<>(); for(int i= 0 ; i < 5000;i++){ list.add(new ArrayList<>()); } Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { for(int i= 0 ; i < 5000;i++){ list.get(0).add(2); list.get(21).add(2); list.get(300).add(2); } } }); Collections.sort(list, new Comparator<List<Integer>>() { @Override public int compare(List<Integer> o1, List<Integer> o2) { return Integer.compare(o1.size(), o2.size()); } }); System.out.println(list); }
Solution
The solution to this example is not no simple. You have to make sure that the list size is not changed while the comparison is being done. It anyways doesn’t make sense to do the sorting on size because the size is changing. So even if the comparison went through without any errors, the result would not be correct.
Complete Example
Here I have created a test array that throws the error.
import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; public class Test { public static void main(String[] args) { sort(2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3); sort(3, 2, 3, 2, 1, 31); sort(3, 2, 2, 2, 2, 3, 2, 3, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); sort(1, 1, 1, 1, 1, 2, 1, 1, 1); sort(1,3); } static void sort(Integer... ints) { List<Integer> list = Arrays.asList(ints); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if (o1 < o2) { return -1; } else { return 1; } } }); System.out.println(list); } }
Output :
In the output we see that the above method doesn’t throw the exception always.So for some cases it does pass
[2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] [1, 2, 2, 3, 3, 31] Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:777) at java.util.TimSort.mergeAt(TimSort.java:514) at java.util.TimSort.mergeCollapse(TimSort.java:441) at java.util.TimSort.sort(TimSort.java:245) at java.util.Arrays.sort(Arrays.java:1438) at java.util.Arrays$ArrayList.sort(Arrays.java:3895) at java.util.Collections.sort(Collections.java:175)