This rule raises an issue when compareTo() or compare() orders values by subtracting integers. Integer subtraction can overflow and reverse the comparison result.

Why is this an issue?

Comparable.compareTo() and Comparator.compare() must return a negative integer, zero, or a positive integer as the first value is less than, equal to, or greater than the second. The magnitude of that result does not matter, only its sign.

A common shortcut is to return the difference of two numeric fields:

return this.value - other.value;

Java integer operators wrap on overflow instead of throwing. When the true difference is larger than Integer.MAX_VALUE or smaller than Integer.MIN_VALUE, the wrapped result can have the wrong sign. For example, (int) (1_000_000_000L - (-1_500_000_000L)) is negative even though the first value is greater.

The same problem appears when a long difference is narrowed to int:

return (int) (this.timestamp - other.timestamp);

That broken ordering violates the comparison contract. Sorting with Arrays.sort() or Collections.sort() can throw IllegalArgumentException: Comparison method violates its general contract!. Ordered collections such as TreeSet and TreeMap can become corrupted.

Use Integer.compare() for int values and Long.compare() for long values. Those methods compare without computing a difference that can overflow.

This rule does not flag floating-point subtraction in ordering methods. See {rule:java:S9148}.

This rule also does not flag subtraction of two values with a provably bounded range, such as array lengths, String.length(), Collection.size(), Enum.ordinal(), or a value masked with a non-negative bitwise AND constant. Their difference cannot overflow, so the subtraction is safe.

How to fix it

Replace the subtraction with Integer.compare() or Long.compare(), matching the operand type.

Code examples

Noncompliant code example

class TimestampedEvent implements Comparable<TimestampedEvent> {
  private long timestamp;

  @Override
  public int compareTo(TimestampedEvent other) {
    return (int) (this.timestamp - other.timestamp); // Noncompliant
  }
}

Compliant solution

class TimestampedEvent implements Comparable<TimestampedEvent> {
  private long timestamp;

  @Override
  public int compareTo(TimestampedEvent other) {
    return Long.compare(this.timestamp, other.timestamp);
  }
}

Noncompliant code example

import java.util.Comparator;

class AgeComparator implements Comparator<Person> {
  @Override
  public int compare(Person left, Person right) {
    return left.age - right.age; // Noncompliant
  }
}

Compliant solution

import java.util.Comparator;

class AgeComparator implements Comparator<Person> {
  @Override
  public int compare(Person left, Person right) {
    return Integer.compare(left.age, right.age);
  }
}

Noncompliant code example

import java.util.List;

void sortById(List<Event> events) {
  events.sort((left, right) -> (int) (left.id - right.id)); // Noncompliant
}

Compliant solution

import java.util.List;

void sortById(List<Event> events) {
  events.sort((left, right) -> Long.compare(left.id, right.id));
}

Resources

Documentation

Related rules