Skip to content

SONARJAVA-4121: Read nullability annotations from the method declaration instead of the substituted type - #5906

Open
andret2344 wants to merge 1 commit into
SonarSource:masterfrom
andret2344:fix/SONARJAVA-4121-optional-orElse-nullability
Open

SONARJAVA-4121: Read nullability annotations from the method declaration instead of the substituted type#5906
andret2344 wants to merge 1 commit into
SonarSource:masterfrom
andret2344:fix/SONARJAVA-4121-optional-orElse-nullability

Conversation

@andret2344

Copy link
Copy Markdown

Fixes the false positive reported in SONARJAVA-4121 (and its twin JAVASE-87).

Discussed in https://community.sonarsource.com/t/false-positive-on-optional-orelse-null/82410

Problem

import java.util.Optional;
import org.eclipse.jdt.annotation.NonNull;

class A {
  private final String value = "value";

  @NonNull // when this is removed, the FP disappears
  public String getValue() {
    return value;
  }

  public String test(A a) {
    return Optional.ofNullable(a)
      .map(A::getValue)
      .orElse(null); // FP: Parameter 1 to this call is marked "@NonNull" but null could be passed
  }
}

org.eclipse.jdt.annotation.NonNull and lombok.NonNull are TYPE_USE annotations, so ECJ keeps them in
the type binding. In the snippet above the type argument is inferred as @NonNull String, the receiver
becomes Optional<@NonNull String> and the substituted signature of orElse becomes
@NonNull String orElse(@NonNull String). With javax.annotation.Nonnull, which is not TYPE_USE, the
FP does not appear.

The frontend copied those inferred annotations onto the symbols of the call:

  • JMethodSymbol#declarationParameters built the parameter placeholder from the substituted parameter
    type, so JSymbolMetadata#of stored @NonNull among the annotations of the parameter. The parameter
    was then reported as NON_NULL at VARIABLE level, which is what S2637 reads.
  • JSymbol#convertMetadata used the substituted return type, so the same annotation ended up on the
    method symbol. Optional#orElse was reported as NON_NULL at METHOD level, which makes the
    symbolic execution engine constrain the result of the call to NOT_NULL.

Note that the annotation only leaks when the type variable is substituted directly. For map, whose
return type is Optional<@NonNull String>, the annotation correctly stays in the type argument metadata.

Fix

Annotations are read from the declared types, while type() and returnType() keep the substituted
types, as checks rely on them. Annotations written explicitly on a declaration, including those on type
arguments of a declared parameter, are unaffected; only annotations inferred at the call site are
dropped.

  • JMethodSymbol and JVariableSymbol: parameter metadata comes from the declared parameter type
  • JSymbol: method metadata comes from the declared return type

Tests

NullabilityWithInferredTypeArgument reproduces the reported snippet and the null check that the leaked
return type annotation would turn into an always-false condition. Two tests in JSymbolMetadataTest
cover the parameter and the method side; both fail without the change:

Expecting empty but was: [org.sonar.java.model.JSymbolMetadata$JAnnotationInstance@5dac488d]

Ran on JDK 26: java-frontend (1595 tests), java-checks-testkit (210 tests) and java-checks
(1706 tests) show no new failures compared to master.

…notations from the method declaration

Annotations inferred for a type variable at the call site were reported as annotations of the method itself, which made Optional.orElse look like it takes a @nonnull argument and never returns null.
@gitar-bot

gitar-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

Updates parameter and return type metadata to read annotations directly from method declarations rather than substituted types, fixing false positive nullability warnings on calls like Optional#orElse. No issues found.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant