Skip to content

JAVASE-241 FPs when Jakarta / Javax NotNull annotation is used - #5990

Closed
asya-vorobeva wants to merge 5 commits into
masterfrom
asya/reuse-testfileclassifier
Closed

JAVASE-241 FPs when Jakarta / Javax NotNull annotation is used#5990
asya-vorobeva wants to merge 5 commits into
masterfrom
asya/reuse-testfileclassifier

Conversation

@asya-vorobeva

Copy link
Copy Markdown
Contributor

Treat Javax / Jakarta Bean Validation @NotNull annotations as WEAK_NULLABLE.

asya-vorobeva and others added 5 commits August 18, 2026 16:43
Delegate path/naming heuristics to TestFileClassifier from sonar-analyzer-commons
and extend it with Java-specific signals (IT paths, filename suffixes/prefix).

- Remove hasTestNamingConvention() and hasTestPathSegment(); replaced by
  TestFileClassifier.of() with JAVA_TEST_PATTERNS covering test/tests/testing
  directory segments, src/it/java, src/its/java, *Test/*Spec/*IT suffixes,
  and the new Test* prefix convention.
- Cache the TestFileClassifier per Configuration via AtomicReference so
  WildcardPatterns are compiled once per analysis run, not once per file.
- Keep isPlatformTestFile() and hasTestFrameworkAnnotation() unchanged;
  public API isTestFile(context) is backward-compatible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The **/Test*.java pattern is too broad and generates too many false
positives (e.g. TestUtils, TestHelper production utilities). Suffix-based
patterns (*Test, *Tests, *TestCase, *IT, *ITCase, *Spec, *Specs) provide
sufficient recall without the noise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s unaffected

When sonar.tests is configured, TestFileClassifier suppresses its path/naming
heuristic because the platform already classifies test files as TEST. Add three
tests to lock in this boundary:
- naming and path signals are ignored when sonar.tests is set
- InputFile.Type.TEST remains authoritative regardless
- annotation signal (hasTestFrameworkAnnotation) is config-independent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add **/IT/java/** and **/ITS/java/** to JAVA_TEST_PATTERNS to cover projects
that use uppercase directory names for integration tests. Update Javadoc and
consolidate the four separate IT/ITS path tests into one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
javax.validation.constraints.NotNull and jakarta.validation.constraints.NotNull
are runtime constraints, not static nullability guarantees. Their previous
NON_NULL classification also caused an inconsistency (SONARJAVA-3803): @NotNull
without arguments resolved to NON_NULL while @NotNull(groups=...) resolved to
UNKNOWN. Moving both to WEAK_NULLABLE gives consistent, conservative treatment.

Rules fixed as a direct consequence:
- S4454: @NotNull on equals() parameter no longer fires (WEAK_NULLABLE is not isNonNull())
- S6539: @NotNull inside @NullMarked no longer flagged as redundant

Rules requiring explicit guards after reclassification:
- S4682: added FQN-based exclusion — BV @NotNull on a primitive return type
  is a validation constraint, not a nullable annotation
- S2638: added isBeanValidationAnnotation() guard in compareNullability() —
  when the upper-bound annotation is a BV annotation, the comparison is skipped
  so that BV @NotNull on a parent param or child return does not incorrectly fire

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

JAVASE-241

Comment on lines +94 to +98
// Bean Validation @NotNull is a runtime constraint, not a static nullability guarantee.
// It is placed here rather than NONNULL_ANNOTATIONS because it cannot serve as a reliable
// static analysis signal (especially when groups= is used), so it is treated conservatively.
"javax.validation.constraints.NotNull",
"jakarta.validation.constraints.NotNull",

@gitar-bot gitar-bot Bot Aug 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: @NotNull reclassified globally causes new FP in Spring S6816

Moving javax/jakarta.validation.constraints.NotNull from NONNULL_ANNOTATIONS to WEAK_NULLABLE_ANNOTATIONS is a global change that affects every rule reading nullabilityData(), not just the two rules patched with explicit exclusions. In S6816 (NullableInjectedFieldsHaveDefaultValueCheck), getNullableAnnotation() returns present whenever an annotation exists and isNonNull() is false; a Spring @Value("${x}") @NotNull field (a very common combination) now satisfies that condition and gets flagged "Provide a default null value for this field" — contradictory advice for a field explicitly required to be non-null. This is a new false positive introduced by the reclassification and is not covered by any test in this PR. Consider either keeping @NotNull out of both the NONNULL and NULLABLE sets (so it resolves to NO_ANNOTATION/UNKNOWN and only the intended rules change), or adding a bean-validation guard to S6816 like the ones added to S2638/S4682.

Guard S6816 against Bean Validation @NotNull (mirroring the exclusion sets added to S2638/S4682).:

private static Optional<AnnotationTree> getNullableAnnotation(VariableTree field) {
  SymbolMetadata.NullabilityData nullabilityData = field.symbol().metadata().nullabilityData(SymbolMetadata.NullabilityTarget.FIELD);
  SymbolMetadata.AnnotationInstance instance = nullabilityData.annotation();
  if (instance == null
      || nullabilityData.isNonNull(SymbolMetadata.NullabilityLevel.CLASS, false, false)
      || isBeanValidationConstraint(instance)) {
    return Optional.empty();
  }
  return Optional.ofNullable(field.symbol().metadata().findAnnotationTree(instance));
}

Was this helpful? React with 👍 / 👎

Comment on lines +94 to +98
// Bean Validation @NotNull is a runtime constraint, not a static nullability guarantee.
// It is placed here rather than NONNULL_ANNOTATIONS because it cannot serve as a reliable
// static analysis signal (especially when groups= is used), so it is treated conservatively.
"javax.validation.constraints.NotNull",
"jakarta.validation.constraints.NotNull",

@gitar-bot gitar-bot Bot Aug 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Bug: Global @NotNull move silently changes S2789/S2447/S4454 behavior

Beyond the two patched rules, the reclassification changes other rules that read nullability with no accompanying tests: S2789 (NullShouldNotBeUsedWithOptionalCheck) will now report an @NotNull Optional<...> method as "should not be @NotNull" (new false positive via isNullable at line 175); S2447 (BooleanMethodReturnCheck) will stop checking @NotNull Boolean methods that return null literals, since !isNullable(...) is now false (new false negative); and S4454 (EqualsParametersMarkedNonNullCheck) stops flagging @NotNull equals parameters (the PR's test change to EqualsParametersMarkedNonNullCheckSample marks this Compliant, so likely intended, but S2789/S2447 appear unintended). Add ruling/test coverage for these rules to confirm the behavior changes are acceptable, or gate the reclassification the same way the two patched rules do.

Avoid globally tagging @NotNull as nullable; scope the special-casing to the intended rules.:

// Prefer not classifying a runtime constraint as a static nullability signal at all:
// keep javax/jakarta validation @NotNull OUT of both WEAK_NULLABLE_ANNOTATIONS and
// NONNULL_ANNOTATIONS so it resolves to NO_ANNOTATION, then only S2638/S4682 need the
// explicit bean-validation handling. This avoids the S2789/S2447/S4454 side effects.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 0 resolved / 2 findings

Treats Bean Validation @NotNull annotations as WEAK_NULLABLE to fix false positives, but global reclassification causes new false positives in Spring S6816 and silently alters behavior in other rules without tests.

⚠️ Bug: @NotNull reclassified globally causes new FP in Spring S6816

📄 java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java:94-98

Moving javax/jakarta.validation.constraints.NotNull from NONNULL_ANNOTATIONS to WEAK_NULLABLE_ANNOTATIONS is a global change that affects every rule reading nullabilityData(), not just the two rules patched with explicit exclusions. In S6816 (NullableInjectedFieldsHaveDefaultValueCheck), getNullableAnnotation() returns present whenever an annotation exists and isNonNull() is false; a Spring @Value("${x}") @NotNull field (a very common combination) now satisfies that condition and gets flagged "Provide a default null value for this field" — contradictory advice for a field explicitly required to be non-null. This is a new false positive introduced by the reclassification and is not covered by any test in this PR. Consider either keeping @NotNull out of both the NONNULL and NULLABLE sets (so it resolves to NO_ANNOTATION/UNKNOWN and only the intended rules change), or adding a bean-validation guard to S6816 like the ones added to S2638/S4682.

Guard S6816 against Bean Validation @NotNull (mirroring the exclusion sets added to S2638/S4682).
private static Optional<AnnotationTree> getNullableAnnotation(VariableTree field) {
  SymbolMetadata.NullabilityData nullabilityData = field.symbol().metadata().nullabilityData(SymbolMetadata.NullabilityTarget.FIELD);
  SymbolMetadata.AnnotationInstance instance = nullabilityData.annotation();
  if (instance == null
      || nullabilityData.isNonNull(SymbolMetadata.NullabilityLevel.CLASS, false, false)
      || isBeanValidationConstraint(instance)) {
    return Optional.empty();
  }
  return Optional.ofNullable(field.symbol().metadata().findAnnotationTree(instance));
}
💡 Bug: Global @NotNull move silently changes S2789/S2447/S4454 behavior

📄 java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java:94-98

Beyond the two patched rules, the reclassification changes other rules that read nullability with no accompanying tests: S2789 (NullShouldNotBeUsedWithOptionalCheck) will now report an @NotNull Optional<...> method as "should not be @NotNull" (new false positive via isNullable at line 175); S2447 (BooleanMethodReturnCheck) will stop checking @NotNull Boolean methods that return null literals, since !isNullable(...) is now false (new false negative); and S4454 (EqualsParametersMarkedNonNullCheck) stops flagging @NotNull equals parameters (the PR's test change to EqualsParametersMarkedNonNullCheckSample marks this Compliant, so likely intended, but S2789/S2447 appear unintended). Add ruling/test coverage for these rules to confirm the behavior changes are acceptable, or gate the reclassification the same way the two patched rules do.

Avoid globally tagging @NotNull as nullable; scope the special-casing to the intended rules.
// Prefer not classifying a runtime constraint as a static nullability signal at all:
// keep javax/jakarta validation @NotNull OUT of both WEAK_NULLABLE_ANNOTATIONS and
// NONNULL_ANNOTATIONS so it resolves to NO_ANNOTATION, then only S2638/S4682 need the
// explicit bean-validation handling. This avoids the S2789/S2447/S4454 side effects.
🤖 Prompt for agents
Code Review: Treats Bean Validation @NotNull annotations as WEAK_NULLABLE to fix false positives, but global reclassification causes new false positives in Spring S6816 and silently alters behavior in other rules without tests.

1. ⚠️ Bug: @NotNull reclassified globally causes new FP in Spring S6816
   Files: java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java:94-98

   Moving `javax/jakarta.validation.constraints.NotNull` from NONNULL_ANNOTATIONS to WEAK_NULLABLE_ANNOTATIONS is a global change that affects every rule reading nullabilityData(), not just the two rules patched with explicit exclusions. In S6816 (NullableInjectedFieldsHaveDefaultValueCheck), getNullableAnnotation() returns present whenever an annotation exists and isNonNull() is false; a Spring `@Value("${x}") @NotNull` field (a very common combination) now satisfies that condition and gets flagged "Provide a default null value for this field" — contradictory advice for a field explicitly required to be non-null. This is a new false positive introduced by the reclassification and is not covered by any test in this PR. Consider either keeping @NotNull out of both the NONNULL and NULLABLE sets (so it resolves to NO_ANNOTATION/UNKNOWN and only the intended rules change), or adding a bean-validation guard to S6816 like the ones added to S2638/S4682.

   Fix (Guard S6816 against Bean Validation @NotNull (mirroring the exclusion sets added to S2638/S4682).):
   private static Optional<AnnotationTree> getNullableAnnotation(VariableTree field) {
     SymbolMetadata.NullabilityData nullabilityData = field.symbol().metadata().nullabilityData(SymbolMetadata.NullabilityTarget.FIELD);
     SymbolMetadata.AnnotationInstance instance = nullabilityData.annotation();
     if (instance == null
         || nullabilityData.isNonNull(SymbolMetadata.NullabilityLevel.CLASS, false, false)
         || isBeanValidationConstraint(instance)) {
       return Optional.empty();
     }
     return Optional.ofNullable(field.symbol().metadata().findAnnotationTree(instance));
   }

2. 💡 Bug: Global @NotNull move silently changes S2789/S2447/S4454 behavior
   Files: java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java:94-98

   Beyond the two patched rules, the reclassification changes other rules that read nullability with no accompanying tests: S2789 (NullShouldNotBeUsedWithOptionalCheck) will now report an `@NotNull Optional<...>` method as "should not be @NotNull" (new false positive via isNullable at line 175); S2447 (BooleanMethodReturnCheck) will stop checking `@NotNull Boolean` methods that return null literals, since `!isNullable(...)` is now false (new false negative); and S4454 (EqualsParametersMarkedNonNullCheck) stops flagging `@NotNull` equals parameters (the PR's test change to EqualsParametersMarkedNonNullCheckSample marks this Compliant, so likely intended, but S2789/S2447 appear unintended). Add ruling/test coverage for these rules to confirm the behavior changes are acceptable, or gate the reclassification the same way the two patched rules do.

   Fix (Avoid globally tagging @NotNull as nullable; scope the special-casing to the intended rules.):
   // Prefer not classifying a runtime constraint as a static nullability signal at all:
   // keep javax/jakarta validation @NotNull OUT of both WEAK_NULLABLE_ANNOTATIONS and
   // NONNULL_ANNOTATIONS so it resolves to NO_ANNOTATION, then only S2638/S4682 need the
   // explicit bean-validation handling. This avoids the S2789/S2447/S4454 side effects.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.
Unblock → Override a blocking verdict and allow merging.

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

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

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

Copy link
Copy Markdown
Contributor

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