Repository: SonarSource/sonar-java
Pull request #5053: SONARJAVA-5383 Lookup Spring-web dependency version to filter out visitors
Author: login: romainbrenguier
Created: 2025-03-11T10:41:10Z | Merged: 2025-04-10T10:05:55Z

[SONARJAVA-5383](https://sonarsource.atlassian.net/browse/SONARJAVA-5383)

Minimal example of what can be achieved by looking at dependency versions.
Improves the SpringComposedRequestMappingCheck

[SONARJAVA-5383]: https://sonarsource.atlassian.net/browse/SONARJAVA-5383?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/.cirrus.yml b/.cirrus.yml
index 48e24b99e7f..87152176818 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -86,7 +86,7 @@ build_task:
   build_script:
     - *log_develocity_url_script
     - source cirrus-env BUILD
-    - regular_mvn_build_deploy_analyze -Dmaven.test.skip=true -Dsonar.skip=true -pl '!java-checks-test-sources/default,!java-checks-test-sources/aws'
+    - regular_mvn_build_deploy_analyze -Dmaven.test.skip=true -Dsonar.skip=true -pl '!java-checks-test-sources/default,!java-checks-test-sources/aws,!java-checks-test-sources/spring-web-4.0'
   cleanup_before_cache_script: cleanup_maven_repository
 
 test_analyze_task:
@@ -118,7 +118,7 @@ ws_scan_task:
   whitesource_script:
     - source cirrus-env QA
     - source set_maven_build_version $BUILD_NUMBER
-    - mvn clean install --batch-mode -Dmaven.test.skip=true -pl '!java-checks-test-sources,!java-checks-test-sources/default,!java-checks-test-sources/aws,!java-checks-test-sources/spring-3.2'
+    - mvn clean install --batch-mode -Dmaven.test.skip=true -pl '!java-checks-test-sources,!java-checks-test-sources/default,!java-checks-test-sources/aws,!java-checks-test-sources/spring-3.2,!java-checks-test-sources/spring-web-4.0'
     - source ws_scan.sh
   allow_failures: "true"
   always:
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 cb7fa747db7..293d3fe5c86 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
@@ -120,7 +120,7 @@ public void javaCheckTestSources() throws Exception {
       .setProjectName(PROJECT_NAME)
       .setProjectVersion("0.1.0-SNAPSHOT")
       .setSourceEncoding("UTF-8")
-      .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/")
+      .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/,spring-web-4.0/src/main/java/")
       .setTestDirs("default/src/test/java/,test-classpath-reader/src/test/java")
       .setProperty("sonar.java.source", "22")
       // common properties
diff --git a/java-checks-test-sources/pom.xml b/java-checks-test-sources/pom.xml
index cf861824b34..99edd30931e 100644
--- a/java-checks-test-sources/pom.xml
+++ b/java-checks-test-sources/pom.xml
@@ -21,6 +21,7 @@
     <module>java-17</module>
     <module>test-classpath-reader</module>
     <module>spring-3.2</module>
+    <module>spring-web-4.0</module>
   </modules>
 
   <build>
diff --git a/java-checks-test-sources/spring-web-4.0/pom.xml b/java-checks-test-sources/spring-web-4.0/pom.xml
new file mode 100644
index 00000000000..299044b417a
--- /dev/null
+++ b/java-checks-test-sources/spring-web-4.0/pom.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.sonarsource.java</groupId>
+    <artifactId>java-checks-test-sources</artifactId>
+    <version>8.13.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>spring-web-4.0</artifactId>
+  <name>SonarQube Java :: Checks Test Sources :: Spring Web 4.0</name>
+
+  <properties>
+    <sonar.skip>true</sonar.skip>
+    <forbiddenapis.skip>true</forbiddenapis.skip>
+    <skipTests>true</skipTests>
+    <maven.deploy.skip>true</maven.deploy.skip>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-web</artifactId>
+      <version>4.0.0.RELEASE</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>analyze-tests</id>
+      <properties>
+        <sonar.skip>false</sonar.skip>
+      </properties>
+    </profile>
+  </profiles>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <release>22</release>
+          <source>22</source>
+          <target>22</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>--enable-preview</argLine>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.simplify4u.plugins</groupId>
+        <artifactId>sign-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>sign-artifacts</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>default-jar</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-javadocs</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-install-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>default-install</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>com.mycila</groupId>
+        <artifactId>license-maven-plugin</artifactId>
+        <configuration>
+          <licenseSets>
+            <licenseSet>
+              <excludes>
+                <exclude>src/main/java/**</exclude>
+                <exclude>src/test/java/**</exclude>
+              </excludes>
+            </licenseSet>
+          </licenseSets>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/java-checks-test-sources/spring-web-4.0/src/main/java/checks/SpringComposedRequestMappingCheckSample.java b/java-checks-test-sources/spring-web-4.0/src/main/java/checks/SpringComposedRequestMappingCheckSample.java
new file mode 100644
index 00000000000..90f277949e4
--- /dev/null
+++ b/java-checks-test-sources/spring-web-4.0/src/main/java/checks/SpringComposedRequestMappingCheckSample.java
@@ -0,0 +1,38 @@
+package checks;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.springframework.web.bind.annotation.RequestMethod.POST;
+
+
+/**
+ * This class serves as a sample for older spring-web version (4.0) where @GetMapping, @PostMapping and so on were not present
+ * hence no issues are expected to be reported here when using the generic @RequestMapping
+ */
+@RestController
+@RequestMapping("/home")
+public class SpringComposedRequestMappingCheckSample {
+
+  @RequestMapping(method = RequestMethod.GET)
+  String m2() {
+    return "";
+  }
+
+  @RequestMapping(method = {POST})
+  String m3() {
+    return "";
+  }
+
+  @RequestMapping(method = {RequestMethod.PUT})
+  String m4() {
+    return "";
+  }
+
+  @RequestMapping(method = RequestMethod.PATCH)
+  String m5() {
+    return "";
+  }
+
+}
diff --git a/java-checks-testkit/src/main/java/org/sonar/java/checks/Constants.java b/java-checks-testkit/src/main/java/org/sonar/java/checks/Constants.java
index 4ce42b52b58..13b4308ffac 100644
--- a/java-checks-testkit/src/main/java/org/sonar/java/checks/Constants.java
+++ b/java-checks-testkit/src/main/java/org/sonar/java/checks/Constants.java
@@ -23,4 +23,6 @@ private Constants() {
 
   public static final String SPRING_3_2 = "../java-checks-test-sources/spring-3.2";
   public static final String SPRING_3_2_CLASSPATH = SPRING_3_2 + "/target/test-classpath.txt";
+  public static final String SPRING_WEB_4_0_TEST_SOURCES = "../java-checks-test-sources/spring-web-4.0";
+  public static final String SPRING_WEB_4_0_CLASSPATH = SPRING_WEB_4_0_TEST_SOURCES + "/target/test-classpath.txt";
 }
diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheck.java
index da3bcba3b01..1414447a85c 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheck.java
@@ -20,10 +20,14 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
 import java.util.stream.Stream;
 import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.DependencyVersionAware;
 import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
 import org.sonar.plugins.java.api.JavaFileScannerContext;
+import org.sonar.plugins.java.api.Version;
 import org.sonar.plugins.java.api.semantic.Symbol;
 import org.sonar.plugins.java.api.tree.AnnotationTree;
 import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
@@ -34,7 +38,7 @@
 import org.sonar.plugins.java.api.tree.Tree;
 
 @Rule(key = "S4488")
-public class SpringComposedRequestMappingCheck extends IssuableSubscriptionVisitor {
+public class SpringComposedRequestMappingCheck extends IssuableSubscriptionVisitor implements DependencyVersionAware {
 
   private static final Map<String, String> PREFERRED_METHOD_MAP = buildPreferredMethodMap();
 
@@ -110,4 +114,11 @@ private static Stream<ExpressionTree> extractValues(ExpressionTree argument) {
     }
     return Stream.of(expression);
   }
+
+  @Override
+  public boolean isCompatibleWithDependencies(Function<String, Optional<Version>> dependencyFinder) {
+    return dependencyFinder.apply("spring-web")
+      .map(v -> v.isGreaterThanOrEqualTo("4.3"))
+      .orElse(false);
+  }
 }
diff --git a/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheckTest.java
index 29fe5cc6e57..6d7d0940601 100644
--- a/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheckTest.java
+++ b/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComposedRequestMappingCheckTest.java
@@ -17,7 +17,11 @@
 package org.sonar.java.checks.spring;
 
 import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.Constants;
 import org.sonar.java.checks.verifier.CheckVerifier;
+import org.sonar.java.test.classpath.TestClasspathUtils;
+
+import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPathInModule;
 
 class SpringComposedRequestMappingCheckTest {
 
@@ -34,4 +38,13 @@ void test() {
       .verifyNoIssues();
   }
 
+  @Test
+  void test_spring_web_4_0() {
+    CheckVerifier.newVerifier()
+      .onFile(mainCodeSourcesPathInModule(Constants.SPRING_WEB_4_0_TEST_SOURCES, "checks/SpringComposedRequestMappingCheckSample.java"))
+      .withCheck(new SpringComposedRequestMappingCheck())
+      .withClassPath(TestClasspathUtils.loadFromFile(Constants.SPRING_WEB_4_0_CLASSPATH))
+      .verifyNoIssues();
+  }
+
 }
diff --git a/java-frontend/src/main/java/org/sonar/java/classpath/DependencyVersionInference.java b/java-frontend/src/main/java/org/sonar/java/classpath/DependencyVersionInference.java
new file mode 100644
index 00000000000..36c8155106c
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/classpath/DependencyVersionInference.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.classpath;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.sonar.plugins.java.api.Version;
+
+public class DependencyVersionInference {
+
+  /** Cache for dependency retrieval. Indexed by artifactId. */
+  private final Map<String, Optional<Version>> dependencyVersionsCache = new HashMap<>();
+
+  static Pattern makeJarPattern(String artifactId) {
+    return Pattern.compile(artifactId + "-" + VersionImpl.VERSION_REGEX + "\\.jar");
+  }
+
+  public Optional<Version> infer(String artifactId, List<File> classpath) {
+    return dependencyVersionsCache
+      .computeIfAbsent(artifactId, key -> infer(makeJarPattern(key), classpath));
+  }
+
+  private static Optional<Version> infer(Pattern jarPattern, List<File> classpath) {
+    for (File file : classpath) {
+      Matcher matcher = jarPattern.matcher(file.getName());
+      if (matcher.matches()) {
+        return Optional.of(VersionImpl.matcherToVersion(matcher));
+      }
+    }
+    return Optional.empty();
+  }
+}
diff --git a/java-frontend/src/main/java/org/sonar/java/classpath/VersionImpl.java b/java-frontend/src/main/java/org/sonar/java/classpath/VersionImpl.java
new file mode 100644
index 00000000000..baf7020ef5d
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/java/classpath/VersionImpl.java
@@ -0,0 +1,125 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.classpath;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+import org.sonar.plugins.java.api.Version;
+
+/**
+ * Class to parse and compare versions of library jars.
+ */
+public record VersionImpl(Integer major, Integer minor, @Nullable Integer patch, @Nullable String qualifier) implements Comparable<Version>, Version {
+
+  public static String VERSION_REGEX = "([0-9]+).([0-9]+)(.[0-9]+)?([^0-9].*)?";
+
+  private static final Pattern VERSION_PATTERN = Pattern.compile(VERSION_REGEX);
+
+  /**
+   * matcher must come from a match against a pattern that contains {@link #VERSION_REGEX} and no other groups.
+   */
+  public static VersionImpl matcherToVersion(Matcher matcher) {
+    return new VersionImpl(
+      Integer.parseInt(matcher.group(1)),
+      Integer.parseInt(matcher.group(2)),
+      matcher.group(3) != null ? Integer.parseInt(matcher.group(3).substring(1)) : null,
+      matcher.group(4));
+  }
+
+  public static VersionImpl parse(String versionString) {
+    Matcher matcher = VERSION_PATTERN.matcher(versionString);
+    if (matcher.matches()) {
+      return matcherToVersion(matcher);
+    }
+    throw new IllegalArgumentException("Not a valid version string: " + versionString);
+  }
+
+  /**
+   * Warning: this is a partial order: 2.5 and 2.5.1 are incomparable.
+   * Qualifiers are ignored.
+   */
+  @Override
+  public int compareTo(Version o) {
+    if (!Objects.equals(major, o.major())) {
+      return major - o.major();
+    }
+    if (!Objects.equals(minor, o.minor())) {
+      return minor - o.minor();
+    }
+    if (!Objects.equals(patch, o.patch())) {
+      if (patch == null || o.patch() == null) return 0;
+      return patch - o.patch();
+    }
+    return 0;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof Version version)) return false;
+    return Objects.equals(major, version.major())
+      && Objects.equals(minor, version.minor())
+      && Objects.equals(patch, version.patch())
+      && Objects.equals(qualifier, version.qualifier());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(major, minor, patch, qualifier);
+  }
+
+  @Override
+  public String toString() {
+    return major + "." + minor +
+      (patch == null ? "" : ("." + patch)) +
+      (qualifier == null ? "" : qualifier);
+  }
+
+  public boolean isGreaterThanOrEqualTo(Version version) {
+    return compareTo(version) >= 0;
+  }
+
+  public boolean isGreaterThan(Version version) {
+    return compareTo(version) > 0;
+  }
+
+  public boolean isLowerThanOrEqualTo(Version version) {
+    return compareTo(version) <= 0;
+  }
+
+  public boolean isLowerThan(Version version) {
+    return compareTo(version) < 0;
+  }
+
+  public boolean isGreaterThanOrEqualTo(String version) {
+    return isGreaterThanOrEqualTo(VersionImpl.parse(version));
+  }
+
+  public boolean isGreaterThan(String version) {
+    return isGreaterThan(VersionImpl.parse(version));
+  }
+
+  public boolean isLowerThanOrEqualTo(String version) {
+    return isLowerThanOrEqualTo(VersionImpl.parse(version));
+  }
+
+  public boolean isLowerThan(String version) {
+    return isLowerThan(VersionImpl.parse(version));
+  }
+
+}
diff --git a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java
index 9c25c6e43ca..e3f500c2df6 100644
--- a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java
+++ b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java
@@ -43,8 +43,10 @@
 import org.sonar.java.ast.visitors.SonarSymbolTableVisitor;
 import org.sonar.java.ast.visitors.SubscriptionVisitor;
 import org.sonar.java.caching.CacheContextImpl;
+import org.sonar.java.classpath.DependencyVersionInference;
 import org.sonar.java.exceptions.ApiMismatchException;
 import org.sonar.java.exceptions.ThrowableUtils;
+import org.sonar.plugins.java.api.DependencyVersionAware;
 import org.sonar.plugins.java.api.InputFileScannerContext;
 import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
 import org.sonar.plugins.java.api.JavaCheck;
@@ -78,6 +80,7 @@ public class VisitorsBridge {
   private int skippedFileCount = 0;
   @VisibleForTesting
   CacheContext cacheContext;
+  private final DependencyVersionInference dependencyService;
 
   @VisibleForTesting
   public VisitorsBridge(JavaFileScanner visitor) {
@@ -98,6 +101,7 @@ public VisitorsBridge(Iterable<? extends JavaCheck> visitors, List<File> project
     this.cacheContext = CacheContextImpl.of(sonarComponents);
 
     this.javaVersion = javaVersion;
+    dependencyService = new DependencyVersionInference();
     updateScanners();
   }
 
@@ -105,12 +109,21 @@ private void updateScanners() {
     allScanners.clear();
     scannersThatCannotBeSkipped.clear();
 
-    allScanners.addAll(filterVisitors(visitors, this::isVisitorJavaVersionCompatible));
+    allScanners.addAll(filterVisitors(visitors, v ->
+      isVisitorJavaVersionCompatible(v) && isVisitorDependencyVersionCompatible(v)));
     if (canSkipScanningOfUnchangedFiles()) {
       scannersThatCannotBeSkipped.addAll(filterVisitors(visitors, this::isUnskippableVisitor));
     }
   }
 
+  private boolean isVisitorDependencyVersionCompatible(Object v) {
+    if (v instanceof DependencyVersionAware versionAware) {
+      return versionAware.isCompatibleWithDependencies(artifactId ->
+        dependencyService.infer(artifactId, classpath));
+    }
+    return true;
+  }
+
   private List<JavaFileScanner> filterVisitors(Iterable<? extends JavaCheck> visitors, Predicate<Object> predicate) {
     List<JavaFileScanner> scanners = new ArrayList<>();
     final IssuableSubscriptionVisitorsRunner runner = new IssuableSubscriptionVisitorsRunner();
@@ -171,7 +184,7 @@ public void setCacheContext(CacheContext cacheContext) {
   /**
    * In cases where incremental analysis is enabled, try to scan a raw file without parsing its content.
    *
-   * @param inputFile    The file to scan
+   * @param inputFile The file to scan
    * @return True if all scanners successfully scan the file without contents. False otherwise.
    */
   public boolean scanWithoutParsing(InputFile inputFile) {
@@ -183,7 +196,7 @@ public boolean scanWithoutParsing(InputFile inputFile) {
       List<JavaFileScanner> scannersNotRequiringParsing = new ArrayList<>();
 
       var fileScannerContext = createScannerContext(sonarComponents, inputFile, javaVersion, inAndroidContext, cacheContext);
-      for (var scanner: scannersThatCannotBeSkipped) {
+      for (var scanner : scannersThatCannotBeSkipped) {
         boolean exceptionIsBlownUp = false;
         PerformanceMeasure.Duration scannerDuration = PerformanceMeasure.start(scanner);
         try {
@@ -288,7 +301,8 @@ private void runScanner(Runnable action, JavaFileScanner scanner) throws CheckFa
       }
 
       String message = String.format(
-        "Unable to run check %s - %s on file '%s', To help improve the SonarSource Java Analyzer, please report this problem to SonarSource: see https://community.sonarsource.com/",
+        "Unable to run check %s - %s on file '%s', To help improve the SonarSource Java Analyzer, please report this problem to SonarSource: see https://community.sonarsource" +
+          ".com/",
         scanner.getClass(), ruleKey(scanner), currentFile);
 
       LOG.error(message, e);
diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java
new file mode 100644
index 00000000000..e40d5a3cdea
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.plugins.java.api;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Implementing this interface allows a check to be executed - or not - during analysis, depending on the
+ * version of desired libraries among dependencies of the current module.
+ *
+ * <p> For example, if a rule is to be used only on modules of a project that use spring-web version at least 4.3,
+ * it should implement {@link DependencyVersionAware} with the following method:
+ * <pre>
+ *  {@literal @}Override
+ *   public boolean isCompatibleWithDependencies(Function<String, Optional<Version>> dependencyFinder) {
+ *     return dependencyFinder.apply("spring-web")
+ *       .map(v -> v.isGreaterThanOrEqualTo("4.3"))
+ *       .orElse(false);
+ *   }
+ * </pre>
+ */
+public interface DependencyVersionAware {
+
+  /**
+   * Control whether the check is compatible with the dependencies of the project being analysed.
+   *
+   * @param dependencyFinder is a function that takes in the name of an artifact that may be found in the classpath
+   *                         of the project. It returns the version of that artifact that was detected, or an empty
+   *                         optional if it is not detected in the classpath.
+   *                         Note that we cannot guarantee that a dependency will always be detected in the case were
+   *                         the classpath doesn't come from a standard build system like maven or gradle.
+   * @return true if the check is compatible with the detected dependencies and should be executed on sources, false otherwise.
+   */
+  boolean isCompatibleWithDependencies(Function<String, Optional<Version>> dependencyFinder);
+
+}
diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/Version.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/Version.java
new file mode 100644
index 00000000000..b54814192a7
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/Version.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.plugins.java.api;
+
+/** Versions of libraries. This provides methods to compare the version with other ones. */
+public interface Version {
+
+  Integer major();
+  Integer minor();
+  Integer patch();
+  String qualifier();
+
+  boolean isGreaterThanOrEqualTo(Version version);
+  boolean isGreaterThanOrEqualTo(String version);
+  boolean isGreaterThan(Version version);
+  boolean isLowerThanOrEqualTo(Version version);
+  boolean isLowerThan(Version version);
+  boolean isGreaterThan(String version);
+  boolean isLowerThanOrEqualTo(String version);
+  boolean isLowerThan(String version);
+}
diff --git a/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java b/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java
new file mode 100644
index 00000000000..720c0996ad7
--- /dev/null
+++ b/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.classpath;
+
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import org.sonar.java.test.classpath.TestClasspathUtils;
+import org.sonar.plugins.java.api.Version;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DependencyVersionInferenceTest {
+
+  @Test
+  void inferLombok() {
+    // Arrange
+    List<File> lombokClasspath = TestClasspathUtils
+      .loadFromFile("../java-checks-test-sources/default/target/test-classpath.txt");
+
+    // Act
+    Optional<Version> version = new DependencyVersionInference().infer("lombok", lombokClasspath);
+
+    // Assert
+    Assertions.assertTrue(version.isPresent());
+    assertEquals(new VersionImpl(1, 18, 30, null), version.get());
+  }
+
+
+  @Test
+  void inferenceSpringBoot() {
+    // Arrange
+    List<File> classpath = TestClasspathUtils
+      .loadFromFile("../java-checks-test-sources/spring-3.2/target/test-classpath.txt");
+
+    // Act
+    Optional<Version> version =
+      new DependencyVersionInference().infer("spring-boot", classpath);
+
+    // Assert
+    Assertions.assertTrue(version.isPresent());
+    assertEquals(new VersionImpl(3, 2, 4, null), version.get());
+  }
+}
diff --git a/java-frontend/src/test/java/org/sonar/java/classpath/VersionTest.java b/java-frontend/src/test/java/org/sonar/java/classpath/VersionTest.java
new file mode 100644
index 00000000000..ffe97716ec0
--- /dev/null
+++ b/java-frontend/src/test/java/org/sonar/java/classpath/VersionTest.java
@@ -0,0 +1,100 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.classpath;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class VersionTest {
+
+  @Test
+  void testCompareTo() {
+    VersionImpl version = VersionImpl.parse("3.2.6-rc1");
+
+    assertTrue(version.isGreaterThanOrEqualTo("3.2"));
+    assertTrue(version.isGreaterThanOrEqualTo("3.2.5"));
+    assertTrue(version.isGreaterThanOrEqualTo("2.9.4-rc2"));
+    assertFalse(version.isGreaterThanOrEqualTo("3.2.11"));
+
+    assertTrue(version.isLowerThan("4.0.0.RELEASE"));
+    assertTrue(version.isLowerThan("3.3.0"));
+    assertFalse(version.isLowerThan("3.2.6-rc1"));
+
+    assertTrue(version.isGreaterThan("3.0.77"));
+    assertFalse(version.isGreaterThan("3.2"));
+    assertFalse(version.isGreaterThan("3.2.6-rc1"));
+
+    assertTrue(version.isLowerThanOrEqualTo("3.2.6-rc1"));
+    assertFalse(version.isLowerThanOrEqualTo("3.2.5"));
+  }
+
+  @Test
+  void testCompareTo_withNoPatchNumber() {
+    VersionImpl version = VersionImpl.parse("3.2");
+
+    assertTrue(version.isGreaterThanOrEqualTo("3.2"));
+    assertTrue(version.isGreaterThanOrEqualTo("3.2.5"));
+    assertTrue(version.isGreaterThanOrEqualTo("2.9.4-rc2"));
+    assertTrue(version.isGreaterThanOrEqualTo("3.2.11"));
+    assertFalse(version.isGreaterThanOrEqualTo("3.3.11"));
+    assertFalse(version.isGreaterThanOrEqualTo("4.0"));
+
+    assertTrue(version.isLowerThan("4.0.0.RELEASE"));
+    assertTrue(version.isLowerThan("3.3.0"));
+    assertFalse(version.isLowerThan("3.2.6-rc1"));
+
+    assertTrue(version.isGreaterThan("3.0.77"));
+    assertFalse(version.isGreaterThan("3.2"));
+    assertFalse(version.isGreaterThan("3.2.6-rc1"));
+
+    assertTrue(version.isLowerThanOrEqualTo("3.2.5"));
+    assertFalse(version.isLowerThanOrEqualTo("3.1"));
+  }
+
+  @Test
+  void testParse() {
+    assertThrows(IllegalArgumentException.class, () -> VersionImpl.parse("foo"));
+  }
+
+  @Test
+  void testToString() {
+    assertEquals("5.4.3-rc1", VersionImpl.parse("5.4.3-rc1").toString());
+    assertEquals("5.43-rc1", VersionImpl.parse("5.43-rc1").toString());
+    assertEquals("5.431", VersionImpl.parse("5.431").toString());
+  }
+
+  @Test
+  void testEqualsAndHashCode() {
+    VersionImpl version1 = VersionImpl.parse("1.2");
+    VersionImpl version2 = VersionImpl.parse("1.2");
+    VersionImpl version3 = VersionImpl.parse("1.2.3");
+
+    assertEquals(version1, version2);
+    assertEquals(version1.hashCode(), version2.hashCode());
+    assertNotEquals(version1, version3);
+    assertNotEquals(version3, version1);
+    assertNotEquals(version1.hashCode(), version3.hashCode());
+    assertFalse(version1.equals("foo"));
+    assertFalse(version1.equals(null));
+
+    VersionImpl version23 = VersionImpl.parse("2.3");
+    VersionImpl version24 = VersionImpl.parse("2.4");
+    assertNotEquals(version1, version23);
+    assertNotEquals(version23, version24);
+  }
+}
diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java
index 010b2264566..7d03eb6068a 100644
--- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java
+++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java
@@ -23,6 +23,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
 import org.assertj.core.api.Fail;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -42,6 +44,7 @@
 import org.sonar.java.exceptions.ApiMismatchException;
 import org.sonar.java.notchecks.VisitorNotInChecksPackage;
 import org.sonar.java.testing.ThreadLocalLogTester;
+import org.sonar.plugins.java.api.DependencyVersionAware;
 import org.sonar.plugins.java.api.InputFileScannerContext;
 import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
 import org.sonar.plugins.java.api.JavaFileScanner;
@@ -49,6 +52,7 @@
 import org.sonar.plugins.java.api.JavaVersion;
 import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
 import org.sonar.plugins.java.api.ModuleScannerContext;
+import org.sonar.plugins.java.api.Version;
 import org.sonar.plugins.java.api.caching.CacheContext;
 import org.sonar.plugins.java.api.internal.EndOfAnalysis;
 import org.sonar.plugins.java.api.tree.CompilationUnitTree;
@@ -283,6 +287,43 @@ public void endOfAnalysis(ModuleScannerContext context) {
     assertThat(trace).containsExactly("RuleForAllJavaVersion", "RuleForJava15", "SubscriptionVisitorForJava10");
   }
 
+  @Test
+  void testfilterScanner_byDependencies() {
+    List<String> trace = new ArrayList<>();
+    class SubscriptionVisitorForSpring8 extends IssuableSubscriptionVisitor implements DependencyVersionAware, EndOfAnalysis {
+
+      @Override
+      public List<Kind> nodesToVisit() {
+        return List.of();
+      }
+
+      @Override
+      public boolean isCompatibleWithDependencies(Function<String, Optional<Version>> dependencyFinder) {
+        return dependencyFinder.apply("spring-core")
+          .map(v -> v.isGreaterThanOrEqualTo("8.0"))
+          .orElse(false);
+      }
+
+      @Override
+      public void endOfAnalysis(ModuleScannerContext context) {
+        trace.add(this.getClass().getSimpleName());
+      }
+    }
+
+    List<SubscriptionVisitorForSpring8> visitors = Collections.singletonList(new SubscriptionVisitorForSpring8());
+    VisitorsBridge visitorsBridge = new VisitorsBridge(visitors, Collections.emptyList(), null);
+    visitorsBridge.endOfAnalysis();
+    assertThat(trace).isEmpty();
+    trace.clear();
+
+
+    visitorsBridge = new VisitorsBridge(visitors, Collections.singletonList(
+      new File("/home/user/.m2/path/spring-core-8.9.12.jar")), null);
+    visitorsBridge.endOfAnalysis();
+    assertThat(trace).containsExactly("SubscriptionVisitorForSpring8");
+    trace.clear();
+  }
+
   @Test
   void canSkipScanningOfUnchangedFiles_returns_false_by_default() {
     VisitorsBridge vb = visitorsBridge(Collections.emptyList(), true);
diff --git a/sonar-java-plugin/src/main/resources/static/documentation.md b/sonar-java-plugin/src/main/resources/static/documentation.md
index 5050d0706e8..71d76bfc6d9 100644
--- a/sonar-java-plugin/src/main/resources/static/documentation.md
+++ b/sonar-java-plugin/src/main/resources/static/documentation.md
@@ -144,6 +144,11 @@ The tutorial [Writing Custom Java Rules 101](https://redirect.sonarsource.com/do
 
 ### API changes
 
+#### **8.12**
+
+* New type: `Version` This will allow comparing different versions of the same artifact, and is used by the new `DependencyVersionAware` interface. 
+* New interface: `DependencyVersionAware`. Implementations of `JavaCheck` that implement this interface will be activated or deactivated depending on the version of dependencies available in the project.
+
 #### **8.10**
 
 * New method: `IssuableSubscriptionVisitor#reportIssue(Tree startTree, Tree endTree, String message, List<JavaFileScannerContext.Location> flow, @Nullable Integer cost)`


Reviews:
user: login: leonardo-pilastri-sonarsource
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:12:36Z
user: login: leonardo-pilastri-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:13:35Z
user: login: leonardo-pilastri-sonarsource
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:44:22Z
user: login: leonardo-pilastri-sonarsource
body: We need to move 1 file, and we should also document the new public APIs in the "API changes" section of the `sonar-java-plugin/src/main/resources/static/documentation.md` file
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T11:11:04Z
user: login: leonardo-pilastri-sonarsource
body: If you also tested that a custom plugin can implement this new interface, besides a couple more javadoc I think it looks good 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:53:47Z
user: login: leonardo-pilastri-sonarsource
body: LGTM now! Thanks for the nice work!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T09:56:25Z

Review comments:
diff_hunk: @@ -120,7 +120,7 @@ public void javaCheckTestSources() throws Exception {
       .setProjectName(PROJECT_NAME)
       .setProjectVersion("0.1.0-SNAPSHOT")
       .setSourceEncoding("UTF-8")
-      .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/")
+      .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/,spring-web-4.0/src/main/java/")
path: its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
user: login: leonardo-pilastri-sonarsource
body: In the root folder of sonar-java we have the .cirrus.yml file where in some parts we exclude test source modules to avoid FPs on Mend for instance, at [line 89 of this yml file for instance](https://github.com/SonarSource/sonar-java/blob/46aa751f30ade47c1bee451e62585f876d195333/.cirrus.yml#L89)

We will need to add this new module in such places as well
created_at: 2025-03-12T13:58:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,4 +23,6 @@ private Constants() {
 
   public static final String SPRING_3_2 = "../java-checks-test-sources/spring-3.2";
   public static final String SPRING_3_2_CLASSPATH = SPRING_3_2 + "/target/test-classpath.txt";
+  public static final String SPRING_WEB_4_0 = "../java-checks-test-sources/spring-web-4.0";
path: java-checks-testkit/src/main/java/org/sonar/java/checks/Constants.java
user: login: leonardo-pilastri-sonarsource
body: Not a strong opinion on this one, I let you decide

We should probably name this/these constants differently, as their usages will look like `Constants.SPRING_WEB_4_0` which is not clear what it represents, to something like `SPRING_WEB_4_0_PROJECT` or `SPRING_WEB_4_0_ROOT`. I don't know if we want to change also the `SPRING_3_2` right now for consistency, or skip it for the sake of smaller scope possible of the PR
created_at: 2025-03-12T14:01:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,125 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.classpath;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/**
+ * Class to parse and compare versions of library jars.
+ */
+public record Version(Integer major, Integer minor, @Nullable Integer patch, @Nullable String qualifier) implements Comparable<Version> {
+
+  public static String VERSION_REGEX = "([0-9]+).([0-9]+)(.[0-9]+)?([^0-9].*)?";
+
+  private static final Pattern VERSION_PATTERN = Pattern.compile(VERSION_REGEX);
+
+  /**
+   * matcher must come from a match again a pattern that contains {@link #VERSION_REGEX} and no other groups.
path: java-frontend/src/main/java/org/sonar/java/classpath/Version.java
user: login: leonardo-pilastri-sonarsource
body: ```suggestion
   * matcher must come from a match against a pattern that contains {@link #VERSION_REGEX} and no other groups.
```
created_at: 2025-03-12T14:04:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,7 +120,7 @@ public void javaCheckTestSources() throws Exception {
       .setProjectName(PROJECT_NAME)
       .setProjectVersion("0.1.0-SNAPSHOT")
       .setSourceEncoding("UTF-8")
-      .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/")
+      .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/,spring-web-4.0/src/main/java/")
path: its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java
user: login: leonardo-pilastri-sonarsource
body: I think it's just at line 89 and 121
created_at: 2025-03-12T14:13:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,125 @@
+/*
path: java-frontend/src/main/java/org/sonar/java/classpath/Version.java
user: login: leonardo-pilastri-sonarsource
body: We have an issue with this class, as it's not part of the public API but the `DependencyVersionAware` depends on it, it will be missing in the classpath for the analysis at runtime if a custom plugin tries to implement the interface.
I think we should also move the `Version` object to the public API.
created_at: 2025-04-04T11:09:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,26 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.plugins.java.api;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+public interface DependencyVersionAware {
path: java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java
user: login: leonardo-pilastri-sonarsource
body: We need to document this API as well
created_at: 2025-04-09T09:49:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,26 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * 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.plugins.java.api;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+public interface DependencyVersionAware {
+
+  boolean isCompatibleWithDependencies(Function<String, Optional<Version>> dependencyFinder);
path: java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java
user: login: leonardo-pilastri-sonarsource
body: Since the usage of this method is not super straight-forward I would document it with a couple of example on how to implement it.
created_at: 2025-04-09T09:52:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqube-next[bot]
created_at: 2025-03-12T14:30:31Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5053) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&resolved=false) [0 New issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5053) [1 Fixed issue](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5053)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&issueStatuses=ACCEPTED) [2 Accepted issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5053) [0 Security Hotspots](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5053)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_coverage&view=list) [99.1% Coverage on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5053)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-25T02:51:41Z
body: This PR is stale because it has been open 7 days with no activity. If there is no activity in the next 7 days it will be closed automatically
author_association: CONTRIBUTOR
user: login: romainbrenguier
created_at: 2025-04-10T09:22:42Z
body: To test that a custom plugin could use the new part of the API, here is the process I followed:
 
* In the java-custom-rules-example module I modified the pom_SQ_10_6_LATEST.xml file to use sonarjava version 8.13.0-SNAPSHOT
* I modified the AvoidAnnotationRule to implement the DependencyVersionAware interface, so that it only applies when spring-web is found with version at least 4.0.0
* I installed sonar-java-plugin and the custom-rules-example
* I moved the two jar files in my local sonarqube installation, in lib/extensions and extensions/plugins respectively
* I started sonarqube and created a sample project, with a profile that included the modified custom rule
* I ran the analysis on a sample project contaning a module with spring-web
* I checked that the custom rule reported an issue in the analysis
* As an additional check, I ran the maven scanner in debug mode and put a break point in the added code of the modified custom rule to check it was behaving as expected

author_association: CONTRIBUTOR
user: login: sonarqube-next[bot]
created_at: 2025-04-10T09:57:25Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5053) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&resolved=false) [0 New issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5053) [1 Fixed issue](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5053)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&issueStatuses=ACCEPTED) [2 Accepted issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5053&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5053) [0 Security Hotspots](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5053)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_coverage&view=list) [99.0% Coverage on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5053&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5053)


author_association: NONE