Skip to content

SONARJAVA-6756: Fix FPs in S8989 for NOT_SUPPORTED and readOnly transactions - #5923

Open
romainbrenguier wants to merge 2 commits into
masterfrom
romain/sonarjava-6756
Open

SONARJAVA-6756: Fix FPs in S8989 for NOT_SUPPORTED and readOnly transactions#5923
romainbrenguier wants to merge 2 commits into
masterfrom
romain/sonarjava-6756

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Summary

  • Do not raise S8989 when @Transactional(propagation = Propagation.NOT_SUPPORTED) or Propagation.NEVER is used, since no transaction is created and rollback configuration is inapplicable
  • Do not raise S8989 when @Transactional(readOnly = true) is used, since read-only transactions perform no write operations
  • Continue raising when @Transactional uses default propagation or readOnly = false

Fixes ~46 out of 50 FPs identified in a 1000-issue sample, reducing the FP rate from 5.0% to ~0.4%.

Test plan

  • Added compliant test cases for propagation = NOT_SUPPORTED, propagation = NEVER, readOnly = true, and combinations
  • Added noncompliant test cases for readOnly = false and propagation = REQUIRED to verify they're still flagged
  • Added class-level annotation tests for both NOT_SUPPORTED and readOnly exclusions
  • Verified existing tests still pass

🤖 Generated with Claude Code

…ctions

Do not raise S8989 when @transactional specifies propagation NOT_SUPPORTED
or NEVER (no transaction is created), or when readOnly = true (no writes
can occur), since rollback configuration is meaningless in these cases.

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

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

Copy link
Copy Markdown
Contributor

SONARJAVA-6756

Comment on lines +228 to +241
private static boolean hasReadOnly(AnnotationTree annotation) {
return annotation.arguments().stream()
.anyMatch(arg -> {
if (arg.is(Tree.Kind.ASSIGNMENT)) {
var assignment = (AssignmentExpressionTree) arg;
String name = ((IdentifierTree) assignment.variable()).name();
if ("readOnly".equals(name)) {
ExpressionTree expression = assignment.expression();
return expression.is(Tree.Kind.BOOLEAN_LITERAL) && "true".equals(((LiteralTree) expression).value());
}
}
return false;
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Edge Case: readOnly=true suppression may hide genuine rollback gaps

readOnly = true is only a hint to the transaction manager/driver; it does not guarantee that no writes occur (e.g. native SQL, some JPA flush scenarios still write). A checked exception thrown during such a write would not trigger rollback without a rollbackFor attribute, so fully suppressing S8989 for readOnly could introduce a false negative. This matches the PR's FP-reduction intent, but consider limiting suppression or documenting the tradeoff.

Was this helpful? React with 👍 / 👎

…ckedExceptionCheck

Add test samples to cover uncovered lines:
- Static-imported propagation constant (IDENTIFIER case in resolveEnumConstantName)
- Method call as propagation value (default case in resolveEnumConstantName)
- Method with non-Transactional annotations in class-level @transactional class
  (exercises annotation loop iteration in getTransactionalAnnotation and isClassLevelAnnotation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Refines S8989 to prevent false positives on NOT_SUPPORTED, NEVER, and readOnly transactions with comprehensive test coverage. Consider that readOnly=true suppression may hide genuine rollback gaps since it is only a driver hint.

💡 Edge Case: readOnly=true suppression may hide genuine rollback gaps

📄 java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java:228-241

readOnly = true is only a hint to the transaction manager/driver; it does not guarantee that no writes occur (e.g. native SQL, some JPA flush scenarios still write). A checked exception thrown during such a write would not trigger rollback without a rollbackFor attribute, so fully suppressing S8989 for readOnly could introduce a false negative. This matches the PR's FP-reduction intent, but consider limiting suppression or documenting the tradeoff.

🤖 Prompt for agents
Code Review: Refines S8989 to prevent false positives on NOT_SUPPORTED, NEVER, and readOnly transactions with comprehensive test coverage. Consider that readOnly=true suppression may hide genuine rollback gaps since it is only a driver hint.

1. 💡 Edge Case: readOnly=true suppression may hide genuine rollback gaps
   Files: java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java:228-241

   `readOnly = true` is only a hint to the transaction manager/driver; it does not guarantee that no writes occur (e.g. native SQL, some JPA flush scenarios still write). A checked exception thrown during such a write would not trigger rollback without a rollbackFor attribute, so fully suppressing S8989 for readOnly could introduce a false negative. This matches the PR's FP-reduction intent, but consider limiting suppression or documenting the tradeoff.

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

@sonarqube-next

Copy link
Copy Markdown

@romainbrenguier
romainbrenguier marked this pull request as ready for review August 14, 2026 08:11
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.

2 participants