FP java:S2637 when collecting stream to list, using JSpecify nullability annotations

In a Java program using JSpecify notations, SonarQube Cloud warns that a method marked @NonNull may return null, when it may not:

package com.example;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

public class SonarExample {
  @NonNull
  public List<String> getStrings() {
    // Sonar says: "This method's return value is marked "@NullMarked at class level" but null is returned."
    return Stream.of("one", "two", "three")
        .map(SonarExample::toUppercase)
        .filter(Objects::nonNull)
        .toList();
  }

  @Nullable
  private static String toUppercase(final String string) {
    if (string.equals("two")) {
      return null;
    }
    return string.toUpperCase();
  }
}

Same happens when using @NullMarked on the class instead of @NonNull on the getStrings method.

If @Nullable is removed on toUppercase, the warning goes away.

Hello @skagedal

Thanks a lot for your feedback.

The current implementation of the S2637 rules is checking the nullability metadata of the methods being called.

In your case if you call “toUppercase” annotated with Nullable, from getStrings return that it’s annotated with NonNull, the nullabilities are not in match therefore sonar raises an exception.

If you remove @Nullable, then sonar is considering that potentially the “toUppercase” is going to be aligned with the caller. Is not evaluating the potential result inside toUppercase method.

Hope this helps.

Hi Jonathan!

Sorry, that does not make quite sense to me.

The method SonarExample::toUpperCase should be annotated with @Nullable, since it can return null. In my reproducer example, it will return null when the input is “two” – of course, this is a contrived example, I was trying to create a minimal reproducer. Removing @Nullable would make it implicitly marked as “non-nullable”, because of the @NullMarked annotation on the class.

Having this method marked as nullable shouldn’t be a problem.

The message from Sonar says “This method’s return value is marked @NonNull but null is returned” – this is simply false. There is no way this getStrings method will return null.

I could more understand if it were that Sonar thought that it may return “a list of possibly null strings”, because it doesn’t know how to prove that filtering with Objects::nonNull guarantees no null entries. But that’s not what it says, and it also doesn’t help if change the return value to be @NonNull List<@Nullable String>.

So I still think it looks like a false positive. :slight_smile:

Hi Jonathan,

I have to agree with Simon on this one. It is affecting multiple rules since our move from the 9.XX version to the 25.XX version.

Sonar no longer seems able to distinguish the annotation on the object @NonNull List. From the annotations linked to the generic types linked to the Object <@Nullable String>.

There is not a single case where it is ok to analyse @NonNull List<@Nullable String> as if it was @Nullable List. The @Nullable annotation here can have no bearing in java on the nullability of the List object.

This is literally causing thousands of false positives on our code base.

I have opened multiple tickets linked to different rules affected by this.

Kind regards,

Hello @skagedal and @nelkahn

I agree with you that is a FP. I was trying to explain how the rule works at the moment.

Currently if the rule finds a Nullable annotation, it is overriding any code execution possibilities.
So in the current code just having a call to Nullable makes the whole stream Nullable, although it’s impossible to be null as you are returning a List. At most it will be a List with no elements.

Here I share the link to the Jira issue I created : Jira

Thank you

Ah, thank you for clarifying, Jonathan! I now read your previous message as you intended it. Thank you.