Comparison method violates its general contract!

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

sort collection

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 comparereturn 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)
Like this post? Don’t forget to share it!