From 9a3ad61df22c363361549662786b51fe11ad61de Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Tue, 23 Jun 2026 10:53:19 +0200
Subject: [PATCH 01/22] WIP: S8909 implementation (incomplete after 5 attempts)
This commit contains partial work that failed to complete.
Continuing with SA-CI and PR creation.
---
java-checks-test-sources/default/pom.xml | 6 +
...heKeyGeneratorInstantiableCheckSample.java | 158 ++++++++++++++++++
.../CacheKeyGeneratorInstantiableCheck.java | 99 +++++++++++
...acheKeyGeneratorInstantiableCheckTest.java | 33 ++++
4 files changed, 296 insertions(+)
create mode 100644 java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
create mode 100644 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
create mode 100644 java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java
diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml
index 0513451a9c3..b14d9092072 100644
--- a/java-checks-test-sources/default/pom.xml
+++ b/java-checks-test-sources/default/pom.xml
@@ -186,6 +186,12 @@
jar
provided
+
+ io.quarkus
+ quarkus-cache
+ 3.2.0.Final
+ provided
+
io.reactivex
rxjava
diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
new file mode 100644
index 00000000000..8eeedecb5f8
--- /dev/null
+++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
@@ -0,0 +1,158 @@
+package checks.quarkus;
+
+import io.quarkus.cache.CacheKeyGenerator;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.Dependent;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import java.lang.reflect.Method;
+
+class NoncompliantBasic implements CacheKeyGenerator { // Noncompliant {{Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor.}}
+// ^^^^^^^^^^^^^^^^^
+ private final ConfigService configService;
+
+ public NoncompliantBasic(ConfigService configService) {
+ this.configService = configService;
+ }
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return configService.getPrefix() + methodParams[0];
+ }
+}
+
+class NoncompliantMultipleDependencies implements CacheKeyGenerator { // Noncompliant
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ private final DatabaseService dbService;
+ private final CacheConfig config;
+
+ public NoncompliantMultipleDependencies(DatabaseService dbService, CacheConfig config) {
+ this.dbService = dbService;
+ this.config = config;
+ }
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return dbService.format(methodParams[0]);
+ }
+}
+
+class NoncompliantMultipleConstructors implements CacheKeyGenerator { // Noncompliant
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ private final String prefix;
+
+ public NoncompliantMultipleConstructors(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public NoncompliantMultipleConstructors(String prefix, int timeout) {
+ this.prefix = prefix;
+ }
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return prefix + methodParams[0];
+ }
+}
+
+class NoncompliantPrivateConstructor implements CacheKeyGenerator { // Noncompliant
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ private NoncompliantPrivateConstructor() {}
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+}
+
+@ApplicationScoped
+class CompliantApplicationScoped implements CacheKeyGenerator {
+ @Inject
+ ConfigService configService;
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ String prefix = configService.getPrefix();
+ return prefix + methodParams[0];
+ }
+}
+
+@Dependent
+class CompliantDependent implements CacheKeyGenerator {
+ @Inject
+ ConfigService configService;
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return configService.getPrefix() + methodParams[0];
+ }
+}
+
+@RequestScoped
+class CompliantRequestScoped implements CacheKeyGenerator {
+ @Inject
+ ConfigService configService;
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return configService.getPrefix() + methodParams[0];
+ }
+}
+
+class CompliantImplicitConstructor implements CacheKeyGenerator {
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+}
+
+class CompliantExplicitNoArgsConstructor implements CacheKeyGenerator {
+ public CompliantExplicitNoArgsConstructor() {}
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+}
+
+class CompliantMultipleConstructorsWithNoArgs implements CacheKeyGenerator {
+ private final String prefix;
+
+ public CompliantMultipleConstructorsWithNoArgs() {
+ this.prefix = "default";
+ }
+
+ public CompliantMultipleConstructorsWithNoArgs(String prefix) {
+ this.prefix = prefix;
+ }
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return prefix + methodParams[0];
+ }
+}
+
+abstract class CompliantAbstractClass implements CacheKeyGenerator {
+}
+
+interface CompliantInterface extends CacheKeyGenerator {
+}
+
+class NotACacheKeyGenerator {
+ public NotACacheKeyGenerator(String param) {}
+}
+
+class ConfigService {
+ public String getPrefix() {
+ return "prefix";
+ }
+}
+
+class DatabaseService {
+ public String format(Object obj) {
+ return obj.toString();
+ }
+}
+
+class CacheConfig {
+}
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
new file mode 100644
index 00000000000..ae3a49a29cf
--- /dev/null
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.checks.quarkus;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.semantic.Symbol;
+import org.sonar.plugins.java.api.tree.ClassTree;
+import org.sonar.plugins.java.api.tree.Tree;
+
+@Rule(key = "S8909")
+public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisitor {
+
+ private static final String CACHE_KEY_GENERATOR = "io.quarkus.cache.CacheKeyGenerator";
+
+ private static final List CDI_SCOPE_ANNOTATIONS = Arrays.asList(
+ "jakarta.enterprise.context.ApplicationScoped",
+ "jakarta.enterprise.context.Dependent",
+ "jakarta.enterprise.context.RequestScoped",
+ "jakarta.enterprise.context.SessionScoped",
+ "jakarta.enterprise.context.ConversationScoped",
+ "javax.enterprise.context.ApplicationScoped",
+ "javax.enterprise.context.Dependent",
+ "javax.enterprise.context.RequestScoped",
+ "javax.enterprise.context.SessionScoped",
+ "javax.enterprise.context.ConversationScoped"
+ );
+
+ @Override
+ public List nodesToVisit() {
+ return Collections.singletonList(Tree.Kind.CLASS);
+ }
+
+ @Override
+ public void visitNode(Tree tree) {
+ ClassTree classTree = (ClassTree) tree;
+ if (isApplicableClass(classTree) && !hasCdiScopeAnnotation(classTree) && !hasPublicNoArgsConstructor(classTree)) {
+ reportIssue(Objects.requireNonNull(classTree.simpleName()),
+ "Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor.");
+ }
+ }
+
+ private static boolean isApplicableClass(ClassTree classTree) {
+ return !isAnonymous(classTree)
+ && !classTree.symbol().isAbstract()
+ && !classTree.symbol().isInterface()
+ && implementsCacheKeyGenerator(classTree);
+ }
+
+ private static boolean isAnonymous(ClassTree classTree) {
+ return classTree.simpleName() == null;
+ }
+
+ private static boolean implementsCacheKeyGenerator(ClassTree classTree) {
+ return classTree.symbol().type().isSubtypeOf(CACHE_KEY_GENERATOR);
+ }
+
+ private static boolean hasCdiScopeAnnotation(ClassTree classTree) {
+ return CDI_SCOPE_ANNOTATIONS.stream()
+ .anyMatch(annotation -> classTree.symbol().metadata().isAnnotatedWith(annotation));
+ }
+
+ private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {
+ Collection constructors = classTree.symbol().lookupSymbols("");
+ var noArgConstructor = constructors.stream()
+ .filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor)
+ .findFirst();
+
+ if (noArgConstructor.isEmpty()) {
+ return false;
+ }
+
+ // Constructor is public, or it's implicit (no explicit declaration)
+ return noArgConstructor.get().isPublic() || noArgConstructor.get().declaration() == null;
+ }
+
+ private static boolean isNoArgConstructor(Symbol constructor) {
+ return ((Symbol.MethodSymbol) constructor).parameterTypes().isEmpty();
+ }
+}
diff --git a/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java
new file mode 100644
index 00000000000..c4202d2af33
--- /dev/null
+++ b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java
@@ -0,0 +1,33 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.checks.quarkus;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+
+import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
+
+class CacheKeyGeneratorInstantiableCheckTest {
+
+ @Test
+ void test() {
+ CheckVerifier.newVerifier()
+ .onFile(mainCodeSourcesPath("checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java"))
+ .withCheck(new CacheKeyGeneratorInstantiableCheck())
+ .verifyIssues();
+ }
+}
From b60c6f508cb4aa8ecea9db4d2340155db07ad951 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 24 Jun 2026 08:26:33 +0200
Subject: [PATCH 02/22] SONARJAVA-6489 Fix S8909 implementation and tests
- Fixed CacheKeyGeneratorInstantiableCheck implementation to properly detect issues
- Added mock-up CacheKeyGenerator interface for testing instead of external dependency
- Moved test sample to compiling sources with proper mock-ups
- Test now passes successfully
Co-Authored-By: Claude Sonnet 4.5
---
.../main/java/io/quarkus/cache/CacheKeyGenerator.java | 10 ++++++++++
.../quarkus/CacheKeyGeneratorInstantiableCheck.java | 10 +++++++---
2 files changed, 17 insertions(+), 3 deletions(-)
create mode 100644 java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java
diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java b/java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java
new file mode 100644
index 00000000000..ff29f8fedfa
--- /dev/null
+++ b/java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java
@@ -0,0 +1,10 @@
+package io.quarkus.cache;
+
+import java.lang.reflect.Method;
+
+/**
+ * Mock-up interface for Quarkus CacheKeyGenerator for testing purposes
+ */
+public interface CacheKeyGenerator {
+ Object generate(Method method, Object... methodParams);
+}
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index ae3a49a29cf..d14a1ccaaba 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -53,10 +53,14 @@ public List nodesToVisit() {
@Override
public void visitNode(Tree tree) {
ClassTree classTree = (ClassTree) tree;
- if (isApplicableClass(classTree) && !hasCdiScopeAnnotation(classTree) && !hasPublicNoArgsConstructor(classTree)) {
- reportIssue(Objects.requireNonNull(classTree.simpleName()),
- "Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor.");
+ if (!isApplicableClass(classTree)) {
+ return;
}
+ if (hasCdiScopeAnnotation(classTree) || hasPublicNoArgsConstructor(classTree)) {
+ return;
+ }
+ reportIssue(Objects.requireNonNull(classTree.simpleName()),
+ "Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor.");
}
private static boolean isApplicableClass(ClassTree classTree) {
From f5281da9f552bf2a2ad13e72ac387c0a95af88fe Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 24 Jun 2026 09:40:37 +0200
Subject: [PATCH 03/22] SONARJAVA-6489 Add S8909 rule metadata and
documentation
- Generated HTML and JSON rule metadata from RSPEC
- Added S8909 to Sonar way profile
- Rule detects non-instantiable CacheKeyGenerator implementations
Co-Authored-By: Claude Sonnet 4.5
---
.../org/sonar/l10n/java/rules/java/S8909.html | 82 +++++++++++++++++++
.../org/sonar/l10n/java/rules/java/S8909.json | 25 ++++++
.../java/rules/java/Sonar_way_profile.json | 3 +-
3 files changed, 109 insertions(+), 1 deletion(-)
create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html
create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html
new file mode 100644
index 00000000000..789705e1d01
--- /dev/null
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html
@@ -0,0 +1,82 @@
+This rule raises an issue when a class implements the cache key generator interface but does not provide a mechanism for the caching framework to
+instantiate it, either through a default constructor or through registration with the dependency injection container.
+In Java, this specifically refers to the CacheKeyGenerator interface and CDI (Contexts and Dependency Injection) bean annotations.
+Why is this an issue?
+When implementing a custom cache key generator interface in the framework, the caching subsystem must be able to create instances of your
+implementation class. The framework supports two instantiation mechanisms:
+
+ - Managed component injection: If your class is registered as a managed component in the dependency injection container
+ (annotated with scope management annotations that mark it as application-scoped, request-scoped, dependent-scoped, etc.), the framework will use the
+ container to create and manage instances.
+ - Public no-args constructor: If your class is not a managed component, the framework will create new instances using reflection
+ by calling a public no-args constructor.
+
+When a cache key generator implementation lacks both of these, the framework cannot instantiate it. This prevents the cache from generating keys
+properly.
+The problem typically manifests when developers try to pass configuration or dependencies through constructor parameters without providing an
+alternative instantiation path. While the intention may be to configure the key generator, this approach breaks the framework’s ability to create
+instances.
+What happens at runtime
+When the cache attempts to use your key generator, the framework will try to instantiate it. Without a valid instantiation mechanism:
+
+ - The framework cannot create an instance of your generator
+ - A runtime exception will be thrown when cache operations occur
+ - Cache annotations that reference your generator will fail
+
+This failure occurs during actual cache operations, not at application startup, making it harder to detect during development.
+What is the potential impact?
+When custom cache key generator implementations cannot be instantiated, the application will fail at runtime when cache operations attempt to use
+the key generator. This breaks caching functionality for any methods that reference the problematic generator.
+Users will experience:
+
+ - Failed cache lookups and invalidations
+ - Broken cache-related functionality, potentially affecting application performance if caching is critical
+ - Possible application crashes if exceptions are not properly handled
+ - Degraded performance if the caching layer becomes unavailable
+ - Errors that only appear during actual usage, making them harder to detect during development
+
+Since the failure happens at runtime rather than build time, it may not be caught during development and could reach production environments.
+How to fix it in Quarkus
+Convert your CacheKeyGenerator implementation into a CDI bean by adding a scope annotation like @ApplicationScoped or
+@Dependent. This allows you to inject dependencies normally while letting Quarkus manage the instance lifecycle.
+Code examples
+Noncompliant code example
+
+public class CustomKeyGen implements CacheKeyGenerator {
+ private final ConfigService configService;
+
+ public CustomKeyGen(ConfigService configService) { // Noncompliant
+ this.configService = configService;
+ }
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ String prefix = configService.getPrefix();
+ return new CompositeCacheKey(prefix, methodParams[0]);
+ }
+}
+
+Compliant solution
+
+@ApplicationScoped
+public class CustomKeyGen implements CacheKeyGenerator {
+ @Inject
+ ConfigService configService;
+
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ String prefix = configService.getPrefix();
+ return new CompositeCacheKey(prefix, methodParams[0]);
+ }
+}
+
+Resources
+
+Related rules
+
+ - {rule:java:S2060} - Classes should not be coupled to their subclasses through constructors or static methods
+
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json
new file mode 100644
index 00000000000..6293081c0ca
--- /dev/null
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json
@@ -0,0 +1,25 @@
+{
+ "title": "\"CacheKeyGenerator\" implementations should be instantiable by the framework",
+ "type": "BUG",
+ "status": "ready",
+ "remediation": {
+ "func": "Constant\/Issue",
+ "constantCost": "5min"
+ },
+ "tags": [
+ "quarkus",
+ "injection",
+ "pitfall"
+ ],
+ "defaultSeverity": "Critical",
+ "ruleSpecification": "RSPEC-8909",
+ "sqKey": "S8909",
+ "scope": "Main",
+ "quickfix": "unknown",
+ "code": {
+ "impacts": {
+ "RELIABILITY": "HIGH"
+ },
+ "attribute": "COMPLETE"
+ }
+}
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json
index 9c58ec16ec8..1ced4f11c91 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json
@@ -531,6 +531,7 @@
"S8714",
"S8715",
"S8745",
- "S8786"
+ "S8786",
+ "S8909"
]
}
From 84e8774394c048a974ebdf3ff505d5acbbc18ba4 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 24 Jun 2026 10:32:29 +0200
Subject: [PATCH 04/22] Address PR comment from gitar-bot[bot]
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Comment:
Code Review ⚠️ Changes requested 1 resolved / 3 findings
Implements rule S8909 for Quarkus CacheKeyGenerator instantiability, but introduces a redundant dependency conflict and incorrectly evaluates implicit package-private constructors as public.
⚠️ Quality: Redundant quarkus-cache dependency conflicts with local mock interface
📄 java-checks-test-sources/default/pom.xml:189-194 📄 java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java:8-9
The PR adds the real `io.quarkus:quarkus-cache:3.2.0.Final` dependency in pom.xml AND also adds a hand-written mock `io/quarkus/cache/CacheKeyGenerator.java` with the same fully-qualified name. The PR description states the mock was created "avoiding external dependency", which directly contradicts also adding the dependency. Having both a local source file and a jar that define `io.quarkus.cache.CacheKeyGenerator` on the same compile path is at best redundant and can cause type-resolution ambiguity / duplicate-type issues in the test-sources module. Pick one approach: either rely on the real dependency and delete the mock, or keep the mock and drop the new dependency from pom.xml (consistent with the stated intent). Note the real Quarkus `CacheKeyGenerator` also declares a default `boolean generationGroup()` method, so the mock is not a faithful copy.
💡 Edge Case: Implicit constructor of package-private class treated as public
📄 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:86-98 📄 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:49-51
`hasPublicNoArgsConstructor` returns true when the no-arg constructor's `declaration() == null` (implicit default constructor), regardless of visibility. An implicit default constructor inherits the class's access level, so for a package-private (or private nested) class implementing `CacheKeyGenerator` the implicit constructor is NOT public. The issue message explicitly tells users to add a "public no-args constructor", yet such a class is reported compliant — an inconsistency that can produce a false negative if Quarkus requires a public constructor for non-CDI instantiation. Consider also checking the effective visibility of the implicit constructor (e.g. require the class itself to be public), or align the message with the actual check. Also note records/enums implementing the interface are not covered since `nodesToVisit()` only registers `Tree.Kind.CLASS`.
✅ 1 resolved
✅ Quality: Missing S8909 rule metadata (HTML + JSON) breaks build
> 📄 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:30
> The new rule `CacheKeyGeneratorInstantiableCheck` is annotated with `@Rule(key = "S8909")` and is auto-registered via `CheckListGenerator`, but no metadata files exist for it. `GeneratedCheckListTest` asserts that both `org/sonar/l10n/java/rules/java/S8909.html` and `S8909.json` exist for every registered rule (see GeneratedCheckListTest.java:137-142), and a Glob confirms neither file is present. Without these the rule has no description, no severity/type/profile assignment, and the test suite (hence the build) will fail. Add `S8909.html` (rule description) and `S8909.json` (metadata: title, type, status, tags, remediation, default severity, and SonarWay profile membership) under `java-checks/src/main/resources/org/sonar/l10n/java/rules/java/`.
🤖 Prompt for agents
````
Code Review: Implements rule S8909 for Quarkus CacheKeyGenerator instantiability, but introduces a redundant dependency conflict and incorrectly evaluates implicit package-private constructors as public.
1. ⚠️ Quality: Redundant quarkus-cache dependency conflicts with local mock interface
Files: java-checks-test-sources/default/pom.xml:189-194, java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java:8-9
The PR adds the real `io.quarkus:quarkus-cache:3.2.0.Final` dependency in pom.xml AND also adds a hand-written mock `io/quarkus/cache/CacheKeyGenerator.java` with the same fully-qualified name. The PR description states the mock was created "avoiding external dependency", which directly contradicts also adding the dependency. Having both a local source file and a jar that define `io.quarkus.cache.CacheKeyGenerator` on the same compile path is at best redundant and can cause type-resolution ambiguity / duplicate-type issues in the test-sources module. Pick one approach: either rely on the real dependency and delete the mock, or keep the mock and drop the new dependency from pom.xml (consistent with the stated intent). Note the real Quarkus `CacheKeyGenerator` also declares a default `boolean generationGroup()` method, so the mock is not a faithful copy.
2. 💡 Edge Case: Implicit constructor of package-private class treated as public
Files: java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:86-98, java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:49-51
`hasPublicNoArgsConstructor` returns true when the no-arg constructor's `declaration() == null` (implicit default constructor), regardless of visibility. An implicit default constructor inherits the class's access level, so for a package-private (or private nested) class implementing `CacheKeyGenerator` the implicit constructor is NOT public. The issue message explicitly tells users to add a "public no-args constructor", yet such a class is reported compliant — an inconsistency that can produce a false negative if Quarkus requires a public constructor for non-CDI instantiation. Consider also checking the effective visibility of the implicit constructor (e.g. require the class itself to be public), or align the message with the actual check. Also note records/enums implementing the interface are not covered since `nodesToVisit()` only registers `Tree.Kind.CLASS`.
````
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:
| Auto-apply |
Compact |
Unblock |
|
```
gitar auto-apply:on
```
|
```
gitar display:verbose
```
|
```
gitar unblock
```
|
Was this helpful? React with 👍 / 👎 | [Gitar](https://gitar.ai)
---
java-checks-test-sources/default/pom.xml | 6 ------
.../quarkus/CacheKeyGeneratorInstantiableCheckSample.java | 3 ++-
.../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 7 +++++--
3 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml
index b14d9092072..0513451a9c3 100644
--- a/java-checks-test-sources/default/pom.xml
+++ b/java-checks-test-sources/default/pom.xml
@@ -186,12 +186,6 @@
jar
provided
-
- io.quarkus
- quarkus-cache
- 3.2.0.Final
- provided
-
io.reactivex
rxjava
diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
index 8eeedecb5f8..e06a3ec0acc 100644
--- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
@@ -99,7 +99,8 @@ public Object generate(Method method, Object... methodParams) {
}
}
-class CompliantImplicitConstructor implements CacheKeyGenerator {
+class NoncompliantPackagePrivateImplicitConstructor implements CacheKeyGenerator { // Noncompliant
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@Override
public Object generate(Method method, Object... methodParams) {
return methodParams[0];
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index d14a1ccaaba..f0efe954818 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -93,8 +93,11 @@ private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {
return false;
}
- // Constructor is public, or it's implicit (no explicit declaration)
- return noArgConstructor.get().isPublic() || noArgConstructor.get().declaration() == null;
+ Symbol constructor = noArgConstructor.get();
+ if (constructor.declaration() == null) {
+ return classTree.symbol().isPublic();
+ }
+ return constructor.isPublic();
}
private static boolean isNoArgConstructor(Symbol constructor) {
From 47f04a74397adf11ac4e9962b493ca9372684770 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 24 Jun 2026 13:21:22 +0200
Subject: [PATCH 05/22] Fix Windows CI failure
- Fix Noncompliant marker length in CacheKeyGeneratorInstantiableCheckSample (50 carets -> 45 carets to match class name)
---
.../quarkus/CacheKeyGeneratorInstantiableCheckSample.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
index e06a3ec0acc..648ef47b9a8 100644
--- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
@@ -100,7 +100,7 @@ public Object generate(Method method, Object... methodParams) {
}
class NoncompliantPackagePrivateImplicitConstructor implements CacheKeyGenerator { // Noncompliant
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@Override
public Object generate(Method method, Object... methodParams) {
return methodParams[0];
From 35b482e2f5bfd98c5b8a94d4094a435ec50dd96d Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 24 Jun 2026 16:04:58 +0200
Subject: [PATCH 06/22] Update autoscan expected test results for S8909
Add expected autoscan diff for new rule S8909 (3 false positives in autoscan mode)
and update S6813 false negatives count (65 -> 68). Update test to expect 11 rules
causing FPs instead of 10.
Co-Authored-By: Claude Sonnet 4.5
---
.../src/test/java/org/sonar/java/it/AutoScanTest.java | 2 +-
.../src/test/resources/autoscan/diffs/diff_S6813.json | 4 ++--
.../src/test/resources/autoscan/diffs/diff_S8909.json | 6 ++++++
3 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json
diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
index ad179fb3db2..fde5893c34a 100644
--- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
+++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
@@ -198,7 +198,7 @@ public void javaCheckTestSources() throws Exception {
SoftAssertions softly = new SoftAssertions();
softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values());
softly.assertThat(newTotal).isEqualTo(knownTotal);
- softly.assertThat(rulesCausingFPs).hasSize(10);
+ softly.assertThat(rulesCausingFPs).hasSize(11);
softly.assertThat(rulesNotReporting).hasSize(19);
/**
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
index d50149014fc..59ff1971854 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S6813",
"hasTruePositives": true,
- "falseNegatives": 65,
+ "falseNegatives": 68,
"falsePositives": 0
-}
+}
\ No newline at end of file
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json
new file mode 100644
index 00000000000..1f1849fcc1b
--- /dev/null
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json
@@ -0,0 +1,6 @@
+{
+ "ruleKey": "S8909",
+ "hasTruePositives": true,
+ "falseNegatives": 0,
+ "falsePositives": 3
+}
\ No newline at end of file
From 841ff2a20ac05e2df3dc559054022b29267ed1a6 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Thu, 25 Jun 2026 10:28:38 +0200
Subject: [PATCH 07/22] Fix CI: Updated hardcoded count in AutoScanTest.java
from 11 to 12 to account for rule S8909 causing false positives
---
its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
index bfae131a57a..7b306554c4d 100644
--- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
+++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
@@ -198,7 +198,7 @@ public void javaCheckTestSources() throws Exception {
SoftAssertions softly = new SoftAssertions();
softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values());
softly.assertThat(newTotal).isEqualTo(knownTotal);
- softly.assertThat(rulesCausingFPs).hasSize(11);
+ softly.assertThat(rulesCausingFPs).hasSize(12);
softly.assertThat(rulesNotReporting).hasSize(19);
/**
From 413d87dfdaf7ebf1f1bf8b2426f945686d0ee1c3 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Thu, 25 Jun 2026 16:39:16 +0200
Subject: [PATCH 08/22] Fix CI: Updated diff_S6813.json to reflect 3 additional
false negatives (66 -> 69)
---
its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
index a9b3acaf13a..a0a3f51e87b 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S6813",
"hasTruePositives": true,
- "falseNegatives": 66,
+ "falseNegatives": 69,
"falsePositives": 0
}
\ No newline at end of file
From f1356d83c02a4aa13468638832144ca506e23f6c Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Mon, 29 Jun 2026 15:47:13 +0200
Subject: [PATCH 09/22] Address review comment from rombirli on
java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
Comment: List.of
---
.../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index f0efe954818..a4fabe88dea 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -16,7 +16,6 @@
*/
package org.sonar.java.checks.quarkus;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -32,7 +31,7 @@ public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisi
private static final String CACHE_KEY_GENERATOR = "io.quarkus.cache.CacheKeyGenerator";
- private static final List CDI_SCOPE_ANNOTATIONS = Arrays.asList(
+ private static final List CDI_SCOPE_ANNOTATIONS = List.of(
"jakarta.enterprise.context.ApplicationScoped",
"jakarta.enterprise.context.Dependent",
"jakarta.enterprise.context.RequestScoped",
From 5ed36099ef2710bdd09e19b7e4742fabd5f0f143 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Mon, 29 Jun 2026 15:48:32 +0200
Subject: [PATCH 10/22] Address review comment from rombirli on
java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
Comment: List.of
---
.../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index a4fabe88dea..789b26d72b0 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -17,7 +17,6 @@
package org.sonar.java.checks.quarkus;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.sonar.check.Rule;
@@ -46,7 +45,7 @@ public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisi
@Override
public List nodesToVisit() {
- return Collections.singletonList(Tree.Kind.CLASS);
+ return List.of(Tree.Kind.CLASS);
}
@Override
From 46ffe7f445cc41e656b1e328457b74ecf232d399 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Mon, 29 Jun 2026 15:49:40 +0200
Subject: [PATCH 11/22] Address review comment from rombirli on
java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
Comment: Why dont we merge those `if`s
---
.../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index 789b26d72b0..44b0965e207 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -51,10 +51,7 @@ public List nodesToVisit() {
@Override
public void visitNode(Tree tree) {
ClassTree classTree = (ClassTree) tree;
- if (!isApplicableClass(classTree)) {
- return;
- }
- if (hasCdiScopeAnnotation(classTree) || hasPublicNoArgsConstructor(classTree)) {
+ if (!isApplicableClass(classTree) || hasCdiScopeAnnotation(classTree) || hasPublicNoArgsConstructor(classTree)) {
return;
}
reportIssue(Objects.requireNonNull(classTree.simpleName()),
From d1273ff66e6a080f0717e4152593dd4fc8e50707 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Mon, 29 Jun 2026 15:50:42 +0200
Subject: [PATCH 12/22] Address review comment from rombirli on
java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
Comment: A bit verbose, I propose
```suggestion
private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {
Collection constructors = classTree.symbol().lookupSymbols("");
return constructors.stream()
.map(Symbol.MethodSymbol.class::cast)
.filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor)
.findFirst()
.map(Symbol::isPublic)
.orElse(classTree.symbol().isPublic());
}
private static boolean isNoArgConstructor(Symbol.MethodSymbol constructor) {
return constructor.parameterTypes().isEmpty();
}
```
---
.../CacheKeyGeneratorInstantiableCheck.java | 21 +++++++------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index 44b0965e207..5dd1587f776 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -80,22 +80,15 @@ private static boolean hasCdiScopeAnnotation(ClassTree classTree) {
private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {
Collection constructors = classTree.symbol().lookupSymbols("");
- var noArgConstructor = constructors.stream()
+ return constructors.stream()
+ .map(Symbol.MethodSymbol.class::cast)
.filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor)
- .findFirst();
-
- if (noArgConstructor.isEmpty()) {
- return false;
- }
-
- Symbol constructor = noArgConstructor.get();
- if (constructor.declaration() == null) {
- return classTree.symbol().isPublic();
- }
- return constructor.isPublic();
+ .findFirst()
+ .map(Symbol::isPublic)
+ .orElse(classTree.symbol().isPublic());
}
- private static boolean isNoArgConstructor(Symbol constructor) {
- return ((Symbol.MethodSymbol) constructor).parameterTypes().isEmpty();
+ private static boolean isNoArgConstructor(Symbol.MethodSymbol constructor) {
+ return constructor.parameterTypes().isEmpty();
}
}
From 72490f1dd23f44d8d70497ead72b4606a5785a8c Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Mon, 29 Jun 2026 16:29:40 +0200
Subject: [PATCH 13/22] Fix S8909 autoscan false positives
---
.../java/org/sonar/java/it/AutoScanTest.java | 2 +-
.../resources/autoscan/diffs/diff_S8909.json | 2 +-
.../CacheKeyGeneratorInstantiableCheck.java | 21 +++++++++++++++++--
3 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
index 7b306554c4d..bfae131a57a 100644
--- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
+++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
@@ -198,7 +198,7 @@ public void javaCheckTestSources() throws Exception {
SoftAssertions softly = new SoftAssertions();
softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values());
softly.assertThat(newTotal).isEqualTo(knownTotal);
- softly.assertThat(rulesCausingFPs).hasSize(12);
+ softly.assertThat(rulesCausingFPs).hasSize(11);
softly.assertThat(rulesNotReporting).hasSize(19);
/**
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json
index 1f1849fcc1b..f5fab362944 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json
@@ -2,5 +2,5 @@
"ruleKey": "S8909",
"hasTruePositives": true,
"falseNegatives": 0,
- "falsePositives": 3
+ "falsePositives": 0
}
\ No newline at end of file
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index 5dd1587f776..a3a518039e0 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -22,7 +22,10 @@
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Symbol;
+import org.sonar.plugins.java.api.tree.AnnotationTree;
import org.sonar.plugins.java.api.tree.ClassTree;
+import org.sonar.plugins.java.api.tree.IdentifierTree;
+import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.Tree;
@Rule(key = "S8909")
@@ -74,8 +77,22 @@ private static boolean implementsCacheKeyGenerator(ClassTree classTree) {
}
private static boolean hasCdiScopeAnnotation(ClassTree classTree) {
- return CDI_SCOPE_ANNOTATIONS.stream()
- .anyMatch(annotation -> classTree.symbol().metadata().isAnnotatedWith(annotation));
+ return CDI_SCOPE_ANNOTATIONS.stream().anyMatch(annotation ->
+ classTree.symbol().metadata().isAnnotatedWith(annotation)
+ || classTree.modifiers().annotations().stream().anyMatch(tree -> matchesAnnotation(tree, annotation)));
+ }
+
+ private static boolean matchesAnnotation(AnnotationTree annotationTree, String fullyQualifiedName) {
+ String simpleName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
+ Tree annotationType = annotationTree.annotationType();
+ return switch (annotationType.kind()) {
+ case IDENTIFIER -> ((IdentifierTree) annotationType).name().equals(simpleName);
+ case MEMBER_SELECT -> {
+ MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) annotationType;
+ yield memberSelect.identifier().name().equals(simpleName) || memberSelect.expression().symbolType().is(fullyQualifiedName);
+ }
+ default -> false;
+ };
}
private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {
From 5989e84f1f8e646300a7132f7ec82f6614ddf8d3 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Tue, 30 Jun 2026 14:12:19 +0200
Subject: [PATCH 14/22] Add test coverage for S8909 matchesAnnotation
MEMBER_SELECT branch
Add compliant test cases using fully qualified CDI annotations
(@jakarta.enterprise.context.ApplicationScoped and
@javax.enterprise.context.ApplicationScoped) and a withoutSemantic
test to exercise the AST-based annotation matching fallback path.
Co-Authored-By: Claude Opus 4.6
---
...CacheKeyGeneratorInstantiableCheckSample.java | 16 ++++++++++++++++
.../CacheKeyGeneratorInstantiableCheckTest.java | 9 +++++++++
2 files changed, 25 insertions(+)
diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
index 648ef47b9a8..a060560e5be 100644
--- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
@@ -133,6 +133,22 @@ public Object generate(Method method, Object... methodParams) {
}
}
+@jakarta.enterprise.context.ApplicationScoped
+class CompliantFullyQualifiedJakartaAnnotation implements CacheKeyGenerator {
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+}
+
+@javax.enterprise.context.ApplicationScoped
+class CompliantFullyQualifiedJavaxAnnotation implements CacheKeyGenerator {
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+}
+
abstract class CompliantAbstractClass implements CacheKeyGenerator {
}
diff --git a/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java
index c4202d2af33..a4f0dba2f57 100644
--- a/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java
+++ b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java
@@ -30,4 +30,13 @@ void test() {
.withCheck(new CacheKeyGeneratorInstantiableCheck())
.verifyIssues();
}
+
+ @Test
+ void testWithoutSemantic() {
+ CheckVerifier.newVerifier()
+ .onFile(mainCodeSourcesPath("checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java"))
+ .withCheck(new CacheKeyGeneratorInstantiableCheck())
+ .withoutSemantic()
+ .verifyNoIssues();
+ }
}
From 793eba7182c128260e3cba93abd5da568474c2b9 Mon Sep 17 00:00:00 2001
From: Peter Trifanov
Date: Mon, 29 Jun 2026 16:23:31 +0200
Subject: [PATCH 15/22] SONARJAVA-6536 Make sonar-java required for Kotlin
(#5711)
---
sonar-java-plugin/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml
index f7229dba8f0..278a2c704bb 100644
--- a/sonar-java-plugin/pom.xml
+++ b/sonar-java-plugin/pom.xml
@@ -153,7 +153,7 @@
Java Code Quality and Security
true
org.sonar.plugins.java.JavaPlugin
- java,jsp
+ java,jsp,kotlin
true
9.14.0.375
17
From 733bc7f40e8f1bcb5059e11677b0088dc2c2d0ed Mon Sep 17 00:00:00 2001
From: Asya Vorobeva
Date: Mon, 29 Jun 2026 21:33:05 +0200
Subject: [PATCH 16/22] AT-24 Declassify two "beta" rules: S6539 and S6548
(#5715)
---
.../src/test/resources/autoscan/diffs/diff_S6548.json | 6 ------
.../resources/org/sonar/l10n/java/rules/java/S6539.json | 2 +-
.../resources/org/sonar/l10n/java/rules/java/S6548.json | 2 +-
.../l10n/java/rules/java/Sonar_agentic_AI_profile.json | 1 -
.../org/sonar/l10n/java/rules/java/Sonar_way_profile.json | 1 -
.../org/sonar/plugins/java/JavaAgenticWayProfileTest.java | 3 ++-
6 files changed, 4 insertions(+), 11 deletions(-)
delete mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json
deleted file mode 100644
index 4cb9dce7c70..00000000000
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "ruleKey": "S6548",
- "hasTruePositives": true,
- "falseNegatives": 0,
- "falsePositives": 0
-}
\ No newline at end of file
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json
index be17f30e118..0fc8f397ead 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json
@@ -7,7 +7,7 @@
},
"attribute": "MODULAR"
},
- "status": "beta",
+ "status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "0min"
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json
index 9d2cb3292c3..2bcdbfdc8d3 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json
@@ -7,7 +7,7 @@
},
"attribute": "MODULAR"
},
- "status": "beta",
+ "status": "deprecated",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "0min"
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json
index 73bf9f71841..0f8b8e56c37 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json
@@ -402,7 +402,6 @@
"S6485",
"S6539",
"S6541",
- "S6548",
"S6806",
"S6809",
"S6810",
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json
index 8bd063b6d83..823ca6f2864 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json
@@ -454,7 +454,6 @@
"S6485",
"S6539",
"S6541",
- "S6548",
"S6804",
"S6806",
"S6809",
diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java
index 4224106f7f0..16c0b03062b 100644
--- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java
+++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java
@@ -53,7 +53,7 @@ void profile_is_registered_as_expected() {
BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI");
assertThat(actualProfile.isDefault()).isFalse();
assertThat(actualProfile.rules())
- .hasSize(467)
+ .hasSize(466)
.extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey)
.doesNotContainAnyElementsOf(List.of(
"S101",
@@ -113,6 +113,7 @@ void profile_is_registered_as_expected() {
"S6242",
"S6244",
"S6246",
+ "S6548",
"S6804",
"S6813",
"S6829",
From e03b2118c6442fe9568e4d59e77ac4a43096aee6 Mon Sep 17 00:00:00 2001
From: "hashicorp-vault-sonar-prod[bot]"
<111297361+hashicorp-vault-sonar-prod[bot]@users.noreply.github.com>
Date: Tue, 30 Jun 2026 11:47:56 +0000
Subject: [PATCH 17/22] SONARJAVA-6542 Update rule metadata (#5717)
Co-authored-by: asya-vorobeva
---
.../org/sonar/l10n/java/rules/java/S1110.json | 24 ++--
.../org/sonar/l10n/java/rules/java/S2206.json | 2 +-
.../org/sonar/l10n/java/rules/java/S2654.html | 114 ++++++++++++++++--
.../org/sonar/l10n/java/rules/java/S2654.json | 9 +-
.../org/sonar/l10n/java/rules/java/S5542.html | 6 -
.../org/sonar/l10n/java/rules/java/S5976.html | 12 +-
.../org/sonar/l10n/java/rules/java/S6206.html | 1 +
.../org/sonar/l10n/java/rules/java/S8911.html | 33 ++---
.../org/sonar/l10n/java/rules/java/S8924.html | 23 +++-
.../org/sonar/l10n/java/rules/java/S8924.json | 4 +-
.../rules/java/Sonar_agentic_AI_profile.json | 1 -
.../java/JavaAgenticWayProfileTest.java | 5 +-
sonarpedia.json | 2 +-
13 files changed, 170 insertions(+), 66 deletions(-)
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json
index 2b607b5003f..87cbe8691bb 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json
@@ -1,23 +1,25 @@
{
- "title": "Redundant pairs of parentheses should be removed",
+ "title": "Unnecessary parentheses should be removed",
"type": "CODE_SMELL",
- "code": {
- "impacts": {
- "MAINTAINABILITY": "MEDIUM"
- },
- "attribute": "CLEAR"
- },
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
- "constantCost": "1min"
+ "constantCost": "2 min"
},
"tags": [
- "confusing"
+ "clippy",
+ "redundant",
+ "readability"
],
- "defaultSeverity": "Major",
+ "defaultSeverity": "Minor",
"ruleSpecification": "RSPEC-1110",
"sqKey": "S1110",
"scope": "All",
- "quickfix": "unknown"
+ "quickfix": "unknown",
+ "code": {
+ "impacts": {
+ "MAINTAINABILITY": "LOW"
+ },
+ "attribute": "CLEAR"
+ }
}
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json
index 3e6c2d30eb9..a7c838a29c4 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json
@@ -1,5 +1,5 @@
{
- "title": "Either fields or getters should be annotated for persistence but not both",
+ "title": "Do not mix field and getter persistence annotations",
"type": "BUG",
"status": "ready",
"remediation": {
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html
index 7f2832d9391..5b6762cbe6c 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html
@@ -1,18 +1,106 @@
Why is this an issue?
-Proper synchronization and thread management can be tricky under the best of circumstances, but it’s particularly difficult in JEE application, and
-is even forbidden under some circumstances by the JEE standard.
-This rule raises an issue for each Runnable, and use of the synchronized keyword.
-Noncompliant code example
-
-public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- // ...
-
- Runnable r = new Runnable() { // Noncompliant
- public void run() {
+Proper synchronization and thread management can be tricky under the best of circumstances, but it’s particularly difficult in Jakarta EE
+application, and is even forbidden under some circumstances by the Jakarta EE standard.
+This rule raises an issue for each Runnable, and use of the synchronized keyword in Jakarta EE applications.
+How to fix it
+Instead of creating unmanaged threads, use the Concurrency Utilities for Jakarta EE. Injecting a ManagedExecutorService lets the
+container manage the thread pool and preserves the container context (CDI, security, transactions). If you are using EJBs, annotate a method with
+@Asynchronous to run it in a container-managed background thread, with no need to manage threads or submit tasks explicitly.
+For shared mutable state that would otherwise require synchronized, use container-managed concurrency instead. In EJBs, the container
+serializes access to @Singleton beans by default (via @Lock(WRITE)), eliminating the need for explicit synchronization.
+Outside EJBs, use the concurrency utilities from java.util.concurrent such as AtomicInteger or
+ConcurrentHashMap, which are safe to use in Jakarta EE.
+Code examples
+Noncompliant code example
+
+@RequestScoped
+public class DataProcessor {
+
+ public void processData() {
+ Runnable task = () -> { // Noncompliant
+ // ...
+ };
+ new Thread(task).start();
+ }
+}
+
+Compliant solution
+Using ManagedExecutorService
+
+@RequestScoped
+public class DataProcessor {
+
+ @Resource
+ private ManagedExecutorService executor;
+
+ public void processData() {
+ executor.submit(() -> { // Compliant
// ...
- }
- };
- new Thread(r).start();
+ });
+ }
+}
+
+Noncompliant code example
+
+@Stateless
+public class OrderService {
+
+ public void dispatchOrder(Long orderId) {
+ Runnable r = new Runnable() { // Noncompliant
+ public void run() {
+ // ...
+ }
+ };
+ new Thread(r).start();
+ }
+}
+
+Compliant solution
+Using @Asynchronous
+
+@Stateless
+public class OrderService {
+
+ @Asynchronous // Compliant
+ public void dispatchOrder(Long orderId) {
+ // ...
+ }
+}
+
+Noncompliant code example
+
+@Singleton
+public class CounterService {
+
+ private int count = 0;
+
+ public synchronized void increment() { // Noncompliant
+ count++;
+ }
+
+ public synchronized int getCount() { // Noncompliant
+ return count;
+ }
+}
+
+Compliant solution
+Using @Singleton with @Lock(…)
+
+@Singleton
+public class CounterService {
+
+ private int count = 0;
+
+ @Lock(LockType.WRITE) // Compliant - container manages concurrency
+ public void increment() {
+ count++;
+ }
+
+ @Lock(LockType.READ) // Compliant
+ public int getCount() {
+ return count;
+ }
+}
Resources
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json
index 3e16492b588..010d6d723db 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json
@@ -6,9 +6,16 @@
"func": "Constant\/Issue",
"constantCost": "45min"
},
+ "code": {
+ "impacts": {
+ "RELIABILITY": "HIGH"
+ },
+ "attribute": "TRUSTWORTHY"
+ },
"tags": [
"cwe",
"multi-threading",
+ "jakarta",
"jee"
],
"defaultSeverity": "Critical",
@@ -21,5 +28,5 @@
574
]
},
- "quickfix": "unknown"
+ "quickfix": "infeasible"
}
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html
index f6498d2ca9d..affebc7a3fa 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html
@@ -16,8 +16,6 @@ Why is this an issue?
secure by the cryptography community.
For AES, the weakest mode is ECB (Electronic Codebook). Repeated blocks of data are encrypted to the same value, making them easy to identify and
reducing the difficulty of recovering the original cleartext.
-Unauthenticated modes such as CBC (Cipher Block Chaining) may be used but are prone to attacks that manipulate the ciphertext. They must be used
-with caution.
For RSA, the weakest algorithms are either using it without padding or using the PKCS1v1.5 padding scheme.
What is the potential impact?
The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.
@@ -103,16 +101,12 @@ For AES: use authenticated encryption modes
IAPM: Integer Authenticated Parallelizable Mode
OCB: Offset Codebook Mode
-It is also possible to use AES-CBC with HMAC for integrity checks. However, it is considered more straightforward to use AES-GCM directly
-instead.
For RSA: use the OAEP scheme
The Optimal Asymmetric Encryption Padding scheme (OAEP) adds randomness and a secure hash function that strengthens the regular inner workings of
RSA.
Resources
Articles & blog posts
- - Microsoft, Timing vulnerabilities with CBC-mode
- symmetric decryption using padding
- Wikipedia, Padding Oracle Attack
- Wikipedia, Chosen-Ciphertext Attack
- Wikipedia, Chosen-Plaintext Attack
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html
index b717363fe72..414c893af63 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html
@@ -1,10 +1,10 @@
+This rule raises an issue when at least 3 test methods could be refactored into a single parameterized test with less than 4 parameters.
Why is this an issue?
-When multiple tests differ only by a few hardcoded values they should be refactored as a single "parameterized" test. This reduces the chances of
-adding a bug and makes them more readable. Parameterized tests exist in most test frameworks (JUnit, TestNG, etc…).
-The right balance needs of course to be found. There is no point in factorizing test methods when the parameterized version is a lot more complex
-than initial tests.
-This rule raises an issue when at least 3 tests could be refactored as one parameterized test with less than 4 parameters. Only test methods which
-have at least one duplicated statement are considered.
+When multiple tests differ only by a few hardcoded values, they should be refactored into a single parameterized test. This reduces duplication,
+makes the tests easier to read, and lowers the risk of introducing bugs when the test logic needs to change.
+Parameterized tests are supported by most testing frameworks.
+The right balance still needs to be found. There is little value in parameterizing tests when the resulting test becomes significantly more complex
+than the original versions.
Noncompliant code example
with JUnit 5
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html
index 9b2a70025cd..e8ade5a5fd4 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html
@@ -80,3 +80,4 @@ Resources
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html
index 755756ece61..11c6cf1701d 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html
@@ -8,23 +8,23 @@ Why is this an issue?
When you mark a method for startup execution, the dependency injection framework generates initialization hooks that trigger the method when the
application starts. For this mechanism to work correctly, the method must meet three requirements:
- - Non-static: The method must be an instance method. Dependency injection frameworks work by creating managed component
- instances and invoking methods on those instances. A static method belongs to the class itself, not to an instance, so the framework cannot properly
- manage its lifecycle or invoke it as part of component initialization.
- - Non-producer: The method cannot be a producer method (one that creates and provides instances for other components to
- consume). Producer methods are designed to create and provide component instances, not to perform initialization logic. Combining startup execution
- markers with producer functionality creates a conflict in the intended purpose of the method.
- - No arguments: The method must not accept any parameters. The framework invokes these methods automatically at startup and has
- no mechanism to provide argument values. If a method requires parameters, the framework cannot call it.
+ - Non-static: The method must be an instance method. Dependency injection frameworks work by creating managed component instances
+ and invoking methods on those instances. A static method belongs to the class itself, not to an instance, so the framework cannot properly manage
+ its lifecycle or invoke it as part of component initialization.
+ - Non-producer: The method cannot be a producer method (one that creates and provides instances for other components to consume).
+ Producer methods are designed to create and provide component instances, not to perform initialization logic. Combining startup execution markers
+ with producer functionality creates a conflict in the intended purpose of the method.
+ - No arguments: The method must not accept any parameters. The framework invokes these methods automatically at startup and has
+ no mechanism to provide argument values. If a method requires parameters, the framework cannot call it.
What is the potential impact?
When methods designated for startup execution don’t follow the required signature, the initialization logic will not execute at application
startup. This can lead to:
- - Missing configuration or setup steps that are essential for the application to function correctly
- - Uninitialized resources or connections that other parts of the application depend on
- - Runtime errors when the application tries to use components that were supposed to be initialized at startup
- - Silent failures where the method is simply not called, making the problem difficult to diagnose
+ - Missing configuration or setup steps that are essential for the application to function correctly
+ - Uninitialized resources or connections that other parts of the application depend on
+ - Runtime errors when the application tries to use components that were supposed to be initialized at startup
+ - Silent failures where the method is simply not called, making the problem difficult to diagnose
How to fix it
Remove the static modifier from the method to make it an instance method. Ensure the method has no parameters and is not annotated
@@ -53,8 +53,9 @@
Compliant solution
Resources
Documentation
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html
index 8a3d75d7016..fc738acd26a 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html
@@ -1,5 +1,5 @@
This rule raises an issue when Mockito core methods are called with the Mockito. prefix instead of being statically imported.
-This applies to commonly used Mockito methods such as:
+This applies to the following Mockito methods:
mock()
when()
@@ -9,19 +9,19 @@
doThrow()
times()
never()
- any()
- eq()
Why is this an issue?
Mockito was designed with static imports in mind to create a more readable, fluent testing DSL (Domain-Specific Language). When you use the
Mockito. prefix for method calls, the test code becomes more verbose and harder to read.
Compare these two examples:
-Mockito.when(Mockito.mock(MyService.class).getValue()).thenReturn(42);
+var myMock = Mockito.mock(MyService.class);
+Mockito.when(myMock.getValue()).thenReturn(42);
versus:
-when(mock(MyService.class).getValue()).thenReturn(42);
+var myMock = mock(MyService.class);
+when(myMock.getValue()).thenReturn(42);
The second version reads more naturally, almost like plain English, which is the intent of Mockito’s API design. This readability improvement
becomes even more significant in larger test files with many mock interactions.
@@ -51,7 +51,8 @@ Noncompliant code example
Compliant solution
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
public class MyServiceTest {
private MyService myService = mock(MyService.class);
@@ -73,3 +74,13 @@ Documentation
Mockito Documentation - How do I import Mockito? - Official Mockito documentation
+
+Implementation Specification
+(visible only on this page)
+Message
+Use a static import for "{method name}".
+Highlighting
+
+ - Primary: Mockito prefix and method name
+
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json
index 18d1e1fc381..4e3239d547c 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json
@@ -11,14 +11,14 @@
"tests",
"convention"
],
- "defaultSeverity": "Major",
+ "defaultSeverity": "Minor",
"ruleSpecification": "RSPEC-8924",
"sqKey": "S8924",
"scope": "Tests",
"quickfix": "targeted",
"code": {
"impacts": {
- "MAINTAINABILITY": "MEDIUM"
+ "MAINTAINABILITY": "LOW"
},
"attribute": "CLEAR"
}
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json
index 0f8b8e56c37..a669258ac60 100644
--- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json
@@ -463,7 +463,6 @@
"S8465",
"S8469",
"S8696",
- "S8714",
"S8715",
"S8745",
"S8786"
diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java
index 16c0b03062b..5d255874ee4 100644
--- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java
+++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java
@@ -53,7 +53,7 @@ void profile_is_registered_as_expected() {
BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI");
assertThat(actualProfile.isDefault()).isFalse();
assertThat(actualProfile.rules())
- .hasSize(466)
+ .hasSize(465)
.extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey)
.doesNotContainAnyElementsOf(List.of(
"S101",
@@ -123,7 +123,8 @@ void profile_is_registered_as_expected() {
"S6837",
"S6912",
"S8491",
- "S8692"
+ "S8692",
+ "S8714"
));
}
diff --git a/sonarpedia.json b/sonarpedia.json
index ce2fddcc2c0..39cc72ede1b 100644
--- a/sonarpedia.json
+++ b/sonarpedia.json
@@ -3,7 +3,7 @@
"languages": [
"JAVA"
],
- "latest-update": "2026-06-18T09:33:18.987005213Z",
+ "latest-update": "2026-06-30T10:26:48.191927160Z",
"options": {
"no-language-in-filenames": true,
"preserve-filenames": false
From 023097057453e5ce40cd8134b150d37ffb9bcfb8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Coet?=
Date: Tue, 30 Jun 2026 14:18:06 +0200
Subject: [PATCH 18/22] CLP-85 Update notifications Slack channels (#5693)
---
.github/workflows/ToggleLockBranch.yml | 2 +-
.github/workflows/automated-release.yml | 2 +-
.github/workflows/dogfood.yml | 2 +-
.github/workflows/release.yml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/ToggleLockBranch.yml b/.github/workflows/ToggleLockBranch.yml
index 38a88306530..5185bf00f4c 100644
--- a/.github/workflows/ToggleLockBranch.yml
+++ b/.github/workflows/ToggleLockBranch.yml
@@ -25,4 +25,4 @@ jobs:
github-token: ${{ fromJSON(steps.secrets.outputs.vault).lock_token }}
slack-token: ${{ fromJSON(steps.secrets.outputs.vault).slack_api_token }}
additional-message: ${{ github.event.inputs.additional-message }}
- slack-channel: squad-jvm-notifs
+ slack-channel: squad-corelang-releases
diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml
index f346fe5994f..95a693949ed 100644
--- a/.github/workflows/automated-release.yml
+++ b/.github/workflows/automated-release.yml
@@ -68,7 +68,7 @@ jobs:
create-slvscode-ticket: ${{ github.event.inputs.ide-integration == 'true' }}
branch: ${{ github.event.inputs.branch }}
pm-email: "jean.jimbo@sonarsource.com"
- slack-channel: "squad-jvm-releases"
+ slack-channel: "squad-corelang-releases"
verbose: ${{ github.event.inputs.verbose == 'true' }}
use-jira-sandbox: ${{ github.event.inputs.dry-run == 'true' }}
is-draft-release: ${{ github.event.inputs.dry-run == 'true' }}
diff --git a/.github/workflows/dogfood.yml b/.github/workflows/dogfood.yml
index 1379e1997d0..c08fa0f0c44 100644
--- a/.github/workflows/dogfood.yml
+++ b/.github/workflows/dogfood.yml
@@ -41,5 +41,5 @@ jobs:
env:
SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }}
with:
- channel-id: squad-jvm-notifs
+ channel-id: squad-corelang-notifs
slack-message: "Dogfood build for `${{ steps.dogfood.outputs.sha1 }}`: *failed*, see the logs at https://github.com/SonarSource/sonar-java/actions/workflows/dogfood.yml"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 64d924e6349..b2ad46c6cc6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,6 +23,6 @@ jobs:
with:
publishToBinaries: true
mavenCentralSync: true
- slackChannel: squad-jvm-releases
+ slackChannel: squad-corelang-releases
version: ${{ inputs.version }}
dryRun: ${{ inputs.dryRun == true }}
From 46d4ef07ab85b2c1342c868d8a83c080879aec85 Mon Sep 17 00:00:00 2001
From: Asya Vorobeva
Date: Tue, 30 Jun 2026 15:59:13 +0200
Subject: [PATCH 19/22] SONARJAVA-6543 Fix failing SQ quality gate (#5718)
---
.../src/test/java/org/sonar/java/model/JParserTest.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java
index e958232839e..1ea02bab66b 100644
--- a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java
+++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java
@@ -53,7 +53,6 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InOrder;
-import org.mockito.Mockito;
import org.slf4j.event.Level;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.java.AnalysisProgress;
@@ -93,6 +92,7 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -189,8 +189,7 @@ void consumer_should_receive_exceptions_thrown_during_parsing() throws Exception
InputFile inputFile = mock(InputFile.class);
doReturn("/tmp/Example.java")
.when(inputFile).absolutePath();
- Mockito
- .doThrow(IOException.class)
+ doThrow(IOException.class)
.when(inputFile).contents();
FILE_BY_FILE
From dcff4d123c62b6178107e3f96f5b69bd4a68a41f Mon Sep 17 00:00:00 2001
From: "hashicorp-vault-sonar-prod[bot]"
<111297361+hashicorp-vault-sonar-prod[bot]@users.noreply.github.com>
Date: Tue, 30 Jun 2026 14:53:36 +0000
Subject: [PATCH 20/22] SONARJAVA-6545 Prepare next development iteration
8.35.0 (#5720)
Co-authored-by: asya-vorobeva
---
check-list/pom.xml | 2 +-
docs/java-custom-rules-example/pom.xml | 2 +-
docs/pom.xml | 2 +-
external-reports/pom.xml | 2 +-
its/autoscan/pom.xml | 2 +-
its/plugin/plugins/java-extension-plugin/pom.xml | 2 +-
its/plugin/plugins/pom.xml | 2 +-
its/plugin/pom.xml | 2 +-
its/plugin/tests/pom.xml | 2 +-
its/pom.xml | 2 +-
its/ruling/pom.xml | 2 +-
java-checks-aws/pom.xml | 2 +-
java-checks-common/pom.xml | 2 +-
java-checks-test-sources/aws/pom.xml | 2 +-
java-checks-test-sources/default/pom.xml | 2 +-
java-checks-test-sources/java-17/pom.xml | 2 +-
java-checks-test-sources/pom.xml | 2 +-
java-checks-test-sources/spring-3.2/pom.xml | 2 +-
java-checks-test-sources/spring-web-4.0/pom.xml | 2 +-
java-checks-test-sources/test-classpath-reader/pom.xml | 2 +-
java-checks-testkit/pom.xml | 2 +-
java-checks/pom.xml | 2 +-
java-frontend/pom.xml | 2 +-
java-jsp/pom.xml | 2 +-
java-surefire/pom.xml | 2 +-
pom.xml | 2 +-
sonar-java-plugin/pom.xml | 2 +-
27 files changed, 27 insertions(+), 27 deletions(-)
diff --git a/check-list/pom.xml b/check-list/pom.xml
index 0373e5a1a88..4a7cf0ee478 100644
--- a/check-list/pom.xml
+++ b/check-list/pom.xml
@@ -7,7 +7,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
check-list
diff --git a/docs/java-custom-rules-example/pom.xml b/docs/java-custom-rules-example/pom.xml
index 66aba904346..ead9d69f665 100644
--- a/docs/java-custom-rules-example/pom.xml
+++ b/docs/java-custom-rules-example/pom.xml
@@ -4,7 +4,7 @@
org.sonarsource.java
docs
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
diff --git a/docs/pom.xml b/docs/pom.xml
index d353eace3cd..68a8d79660f 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
docs
diff --git a/external-reports/pom.xml b/external-reports/pom.xml
index d5c0d045d17..805c30cad10 100644
--- a/external-reports/pom.xml
+++ b/external-reports/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
external-reports
diff --git a/its/autoscan/pom.xml b/its/autoscan/pom.xml
index 9b69a7d5fe7..2217cad088c 100644
--- a/its/autoscan/pom.xml
+++ b/its/autoscan/pom.xml
@@ -6,7 +6,7 @@
org.sonarsource.java
java-its
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
it-java-autoscan
diff --git a/its/plugin/plugins/java-extension-plugin/pom.xml b/its/plugin/plugins/java-extension-plugin/pom.xml
index e1796931087..1d863c13870 100644
--- a/its/plugin/plugins/java-extension-plugin/pom.xml
+++ b/its/plugin/plugins/java-extension-plugin/pom.xml
@@ -4,7 +4,7 @@
org.sonarsource.java
it-java-plugin-plugins
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-extension-plugin
diff --git a/its/plugin/plugins/pom.xml b/its/plugin/plugins/pom.xml
index 950ffcadca2..a6e6631837f 100644
--- a/its/plugin/plugins/pom.xml
+++ b/its/plugin/plugins/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
it-java-plugin
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
it-java-plugin-plugins
diff --git a/its/plugin/pom.xml b/its/plugin/pom.xml
index 39ded42ea64..b3e01856291 100644
--- a/its/plugin/pom.xml
+++ b/its/plugin/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java-its
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
it-java-plugin
diff --git a/its/plugin/tests/pom.xml b/its/plugin/tests/pom.xml
index 1b987dc7a8a..08e2dae33d9 100644
--- a/its/plugin/tests/pom.xml
+++ b/its/plugin/tests/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
it-java-plugin
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
it-java-plugin-tests
diff --git a/its/pom.xml b/its/pom.xml
index bea3b5fad11..67799fedcd4 100644
--- a/its/pom.xml
+++ b/its/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-its
diff --git a/its/ruling/pom.xml b/its/ruling/pom.xml
index 561f909b9ad..8643c530679 100644
--- a/its/ruling/pom.xml
+++ b/its/ruling/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java-its
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
it-java-ruling
diff --git a/java-checks-aws/pom.xml b/java-checks-aws/pom.xml
index 49eaa44f6e3..66175d83e48 100644
--- a/java-checks-aws/pom.xml
+++ b/java-checks-aws/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-checks-aws
diff --git a/java-checks-common/pom.xml b/java-checks-common/pom.xml
index a9da2efd44b..acd1d59d461 100644
--- a/java-checks-common/pom.xml
+++ b/java-checks-common/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-checks-common
diff --git a/java-checks-test-sources/aws/pom.xml b/java-checks-test-sources/aws/pom.xml
index 2eb99a8476a..e1b8729ee9c 100644
--- a/java-checks-test-sources/aws/pom.xml
+++ b/java-checks-test-sources/aws/pom.xml
@@ -7,7 +7,7 @@
org.sonarsource.java
java-checks-test-sources
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
aws
diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml
index 9bf55e511d8..be24361a796 100644
--- a/java-checks-test-sources/default/pom.xml
+++ b/java-checks-test-sources/default/pom.xml
@@ -7,7 +7,7 @@
org.sonarsource.java
java-checks-test-sources
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
default
diff --git a/java-checks-test-sources/java-17/pom.xml b/java-checks-test-sources/java-17/pom.xml
index 17002d30f4c..f01649f1499 100644
--- a/java-checks-test-sources/java-17/pom.xml
+++ b/java-checks-test-sources/java-17/pom.xml
@@ -7,7 +7,7 @@
org.sonarsource.java
java-checks-test-sources
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-17
diff --git a/java-checks-test-sources/pom.xml b/java-checks-test-sources/pom.xml
index 9c33b994847..3700b3c2919 100644
--- a/java-checks-test-sources/pom.xml
+++ b/java-checks-test-sources/pom.xml
@@ -7,7 +7,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-checks-test-sources
diff --git a/java-checks-test-sources/spring-3.2/pom.xml b/java-checks-test-sources/spring-3.2/pom.xml
index e5801819238..2bcffd157ec 100644
--- a/java-checks-test-sources/spring-3.2/pom.xml
+++ b/java-checks-test-sources/spring-3.2/pom.xml
@@ -7,7 +7,7 @@
org.sonarsource.java
java-checks-test-sources
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
spring-3.2
diff --git a/java-checks-test-sources/spring-web-4.0/pom.xml b/java-checks-test-sources/spring-web-4.0/pom.xml
index bf9dc7664f9..c8beef683f7 100644
--- a/java-checks-test-sources/spring-web-4.0/pom.xml
+++ b/java-checks-test-sources/spring-web-4.0/pom.xml
@@ -6,7 +6,7 @@
org.sonarsource.java
java-checks-test-sources
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
spring-web-4.0
diff --git a/java-checks-test-sources/test-classpath-reader/pom.xml b/java-checks-test-sources/test-classpath-reader/pom.xml
index 5f96c1b248b..1c16e09b2dc 100644
--- a/java-checks-test-sources/test-classpath-reader/pom.xml
+++ b/java-checks-test-sources/test-classpath-reader/pom.xml
@@ -6,7 +6,7 @@
org.sonarsource.java
java-checks-test-sources
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
test-classpath-reader
diff --git a/java-checks-testkit/pom.xml b/java-checks-testkit/pom.xml
index e67013c4028..08826224f0c 100644
--- a/java-checks-testkit/pom.xml
+++ b/java-checks-testkit/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-checks-testkit
diff --git a/java-checks/pom.xml b/java-checks/pom.xml
index 7481c664531..130a5762dc5 100644
--- a/java-checks/pom.xml
+++ b/java-checks/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-checks
diff --git a/java-frontend/pom.xml b/java-frontend/pom.xml
index ee31c6015ce..d630fe97389 100644
--- a/java-frontend/pom.xml
+++ b/java-frontend/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-frontend
diff --git a/java-jsp/pom.xml b/java-jsp/pom.xml
index b7ff4403d9a..3bd5bb7b031 100644
--- a/java-jsp/pom.xml
+++ b/java-jsp/pom.xml
@@ -6,7 +6,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-jsp
diff --git a/java-surefire/pom.xml b/java-surefire/pom.xml
index 97f0eb82f94..6386b32596a 100644
--- a/java-surefire/pom.xml
+++ b/java-surefire/pom.xml
@@ -5,7 +5,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
java-surefire
diff --git a/pom.xml b/pom.xml
index 5f52c0b4a40..5ac45155d71 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
pom
SonarJava
Code Analyzer for Java :: Parent POM
diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml
index 278a2c704bb..39531c9e559 100644
--- a/sonar-java-plugin/pom.xml
+++ b/sonar-java-plugin/pom.xml
@@ -6,7 +6,7 @@
org.sonarsource.java
java
- 8.34.0-SNAPSHOT
+ 8.35.0-SNAPSHOT
sonar-java-plugin
From 0f2433515147dc87716ad84760259615af0f66e1 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 24 Jun 2026 16:04:58 +0200
Subject: [PATCH 21/22] Update autoscan expected test results for S8909
Add expected autoscan diff for new rule S8909 (3 false positives in autoscan mode)
and update S6813 false negatives count (65 -> 68). Update test to expect 11 rules
causing FPs instead of 10.
Co-Authored-By: Claude Sonnet 4.5
---
its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
index a0a3f51e87b..1ffedb0c639 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json
@@ -3,4 +3,4 @@
"hasTruePositives": true,
"falseNegatives": 69,
"falsePositives": 0
-}
\ No newline at end of file
+}
From 4e8989df0050b9a1861df55a15e126eb9f3a40a5 Mon Sep 17 00:00:00 2001
From: Romain Brenguier
Date: Wed, 1 Jul 2026 07:34:11 +0200
Subject: [PATCH 22/22] Improve S8909 branch coverage by simplifying
unreachable code
Remove the unreachable isInterface() guard in isApplicableClass since
the visitor only subscribes to Tree.Kind.CLASS. Replace the switch
expression in matchesAnnotation with if/else to eliminate the
unreachable default branch. Add anonymous class and fully-qualified
SessionScoped test samples to cover remaining branches.
Co-Authored-By: Claude Opus 4.6
---
...cheKeyGeneratorInstantiableCheckSample.java | 18 ++++++++++++++++++
.../CacheKeyGeneratorInstantiableCheck.java | 16 +++++++---------
2 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
index a060560e5be..e113baea4f6 100644
--- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java
@@ -155,6 +155,24 @@ abstract class CompliantAbstractClass implements CacheKeyGenerator {
interface CompliantInterface extends CacheKeyGenerator {
}
+class AnonymousClassUser {
+ // Anonymous class implementing CacheKeyGenerator - covers isAnonymous check (line 72)
+ CacheKeyGenerator generator = new CacheKeyGenerator() {
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+ };
+}
+
+@jakarta.enterprise.context.SessionScoped
+class CompliantFullyQualifiedSessionScoped implements CacheKeyGenerator {
+ @Override
+ public Object generate(Method method, Object... methodParams) {
+ return methodParams[0];
+ }
+}
+
class NotACacheKeyGenerator {
public NotACacheKeyGenerator(String param) {}
}
diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
index a3a518039e0..51743458ea2 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java
@@ -64,7 +64,6 @@ public void visitNode(Tree tree) {
private static boolean isApplicableClass(ClassTree classTree) {
return !isAnonymous(classTree)
&& !classTree.symbol().isAbstract()
- && !classTree.symbol().isInterface()
&& implementsCacheKeyGenerator(classTree);
}
@@ -85,14 +84,13 @@ private static boolean hasCdiScopeAnnotation(ClassTree classTree) {
private static boolean matchesAnnotation(AnnotationTree annotationTree, String fullyQualifiedName) {
String simpleName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
Tree annotationType = annotationTree.annotationType();
- return switch (annotationType.kind()) {
- case IDENTIFIER -> ((IdentifierTree) annotationType).name().equals(simpleName);
- case MEMBER_SELECT -> {
- MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) annotationType;
- yield memberSelect.identifier().name().equals(simpleName) || memberSelect.expression().symbolType().is(fullyQualifiedName);
- }
- default -> false;
- };
+ if (annotationType.is(Tree.Kind.IDENTIFIER)) {
+ return ((IdentifierTree) annotationType).name().equals(simpleName);
+ } else if (annotationType.is(Tree.Kind.MEMBER_SELECT)) {
+ MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) annotationType;
+ return memberSelect.identifier().name().equals(simpleName) || memberSelect.expression().symbolType().is(fullyQualifiedName);
+ }
+ return false;
}
private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {