[Java][S2200] False Negative: Rule does not detect compareTo() result when wrapped in type cast (e.g

What language is this for?

Java

Which rule?

  • Rule Key: java:S2200
  • Rule Name: “compareTo” results should not be checked for specific values

Why do you believe it’s a false-negative?

According to the Comparable contract and the rule documentation, the result of compareTo() should only be checked for its sign (> 0, < 0, or == 0). Checking for specific numeric values such as 1 or -1 is unreliable because the exact magnitude is an implementation detail and can vary between classes, JDK versions, or even different compareTo implementations.

The current rule correctly detects direct calls and variable references:

if (a.compareTo(b) == 1) { ... }           // detected
int result = a.compareTo(b);
if (result == 1) { ... }                   // detected

However, it completely misses cases where the compareTo() result is wrapped in a type cast (Tree.Kind.TYPE_CAST), which is a very common pattern in real code (explicit casts for clarity, autoboxing, or legacy code).

The following patterns are not detected at all (False Negative), even though they have exactly the same runtime semantics as the cases the rule already catches.

Are you using

  • SonarQube Server / Community Build - latest version

  • SonarJava analyzer - latest version

(I am not using SonarQube Cloud or SonarQube for IDE in this report.)

How can we reproduce the problem?

Here is a self-contained, fully compilable Java 8+ snippet:

Java

import java.util.Comparator;

public class S2200FalseNegativeDemo {
    static class Person implements Comparable<Person> {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public int compareTo(Person other) {
            return Integer.compare(this.age, other.age);
        }
    }

    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Bob", 30);

        // ❌ These three cases are NOT detected (False Negative)
        if ((int) p1.compareTo(p2) == 1) {              // should raise java:S2200
            System.out.println("p1 is greater");
        }

        if ((Integer) p1.compareTo(p2) == -1) {         // should raise java:S2200
            System.out.println("p1 is less");
        }

        if (((int) p1.compareTo(p2)) == 1) {            // should raise java:S2200
            System.out.println("p1 is greater");
        }

        // ✅ Only these patterns are currently detected
        if (p1.compareTo(p2) == 1) { }                  // detected
    }
}

Expected behavior: All three type-cast cases above should raise java:S2200.

Additional technical detail (for the development team):
The root cause is in CompareToResultTestCheck.java:

Java

private static boolean isCompareToResult(ExpressionTree expression) {
    if (expression.is(Tree.Kind.METHOD_INVOCATION)) {
        return COMPARE_TO.matches((MethodInvocationTree) expression);
    }
    if (expression.is(Tree.Kind.IDENTIFIER)) {
        return isIdentifierContainingCompareToResult((IdentifierTree) expression);
    }
    return false;   // ← no handling for Tree.Kind.TYPE_CAST
}

visitNode only calls ExpressionUtils.skipParentheses() and never skips or recurses into type casts. The check should use (or implement) logic similar to ExpressionUtils.skipParenthesesAndCasts() to extract the inner compareTo call.

I searched the SonarSource community forum and both sonar-java / sonarqube GitHub repositories before posting — no existing issue matches this specific false negative scenario.

Thank you for your help!

text


I don’t agree that (int) p1.compareTo(p2) is clearer than p1.compareTo(p2). On the contrary, the cast makes me suspect that this compareTo is not the standard one, because why would someone write a redundant cast?

If the code is (Integer) p1.compareTo(p2) it is even worse, hoping that the JVM will optimize out the allocation of the box.

Rule S1905 will flag redundant casts; I suggest fixing those issues :wink:

If it is necessary to clarify the type of something, a local variable would be a better option, since it does not force as many fallible conversions:

int result = p1.compareTo(p2);
if (result > 0) { }

and the rule already detects this pattern.

Hi @leemeii and @jilles-sg

Thank you both for the detailed report and for sharing your insights on this!

@jilles-sg, I completely agree with you on the code quality aspect. Writing (int) or (Integer) around a compareTo result is redundant and rightfully flagged by rule S1905 (Redundant Casts).

However, a false negative is still a false negative. If a developer suppresses the S1905 warning, the dangerous `compareTo` bug remains hidden. SonarQube should peel back the cast and trigger both rules.

To track this false negative, I have created this Jira ticket: SONARJAVA-6380

Thanks again for the great discussion!