Repository: SonarSource/sonar-java
Pull request #5087: SONARJAVA-5454 Implement S7467: replace Unused exception parameter with the unnamed pattern
Author: login: erwan-serandour
Created: 2025-04-16T08:50:05Z | Merged: 2025-04-17T07:43:31Z

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

Part of 
<!-- 
  Only for standalone PRs without Jira issue in the PR title: 
    * Replace this comment with Epic ID to create a new Task in Jira
    * Replace this comment with Issue ID to create a new Sub-Task in Jira
    * Ignore or delete this note to create a new Task in Jira without a parent 
-->


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


Diff:
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S7467.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S7467.json
new file mode 100644
index 00000000000..44c92cdf884
--- /dev/null
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S7467.json
@@ -0,0 +1,6 @@
+{
+  "ruleKey": "S7467",
+  "hasTruePositives": true,
+  "falseNegatives": 0,
+  "falsePositives": 0
+}
\ No newline at end of file
diff --git a/java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java
new file mode 100644
index 00000000000..1a670d0db81
--- /dev/null
+++ b/java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java
@@ -0,0 +1,124 @@
+package checks;
+
+import io.restassured.exception.PathException;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample {
+  public void simpleNonCompliant() {
+
+    List<String> elements = List.of();
+    int value = 0;
+    try {
+      var elem = elements.get(10);
+      value = Integer.parseInt(elem);
+    } catch (NumberFormatException nfe) { // Noncompliant {{Replace "nfe" with an unnamed pattern.}}
+      //                           ^^^
+      //                           fix@qf1 {{Replace "nfe" with "_"}}
+      //                           edit@qf1 [[sc=36;ec=39]] {{_}}
+      System.err.println("Wrong number format");
+    } catch (IndexOutOfBoundsException ioob) { // Noncompliant {{Replace "ioob" with an unnamed pattern.}}
+      //                               ^^^^
+      //                               fix@qf2 {{Replace "ioob" with "_"}}
+      //                               edit@qf2 [[sc=40;ec=44]] {{_}}
+      System.err.println("No such element");
+    }
+  }
+
+  public void simpleCompliant() {
+    List<String> elements = List.of();
+    int value = 0;
+    try {
+      var elem = elements.get(10);
+      value = Integer.parseInt(elem);
+    } catch (NumberFormatException _) { // compliant
+      System.err.println("Wrong number format");
+    } catch (IndexOutOfBoundsException _) { // compliant
+      System.err.println("No such element");
+    }
+  }
+
+  public void severalExceptionNonCompliant() {
+    try {
+
+    } catch (NumberFormatException | IndexOutOfBoundsException e) { // Noncompliant
+
+    } catch (PathException | ClassCastException e) { // Noncompliant
+
+    }
+  }
+
+  public void severalExceptionCompliant() {
+    try {
+
+    } catch (NumberFormatException | IndexOutOfBoundsException _) { // compliant
+
+    } catch ( PathException | ClassCastException _) { // compliant
+
+    }
+  }
+
+  public void nestedExceptionNonCompliant() {
+    try {
+    } catch (IndexOutOfBoundsException e) { // Noncompliant
+      try {
+
+      } catch (NumberFormatException k) { // Noncompliant
+        try {
+
+        } catch (PathException _) {
+
+        }
+      }
+    }
+  }
+
+  public void codeInsideCatchBlockNonCompliant() {
+    try {
+    } catch (Exception e) { // Noncompliant
+      int x = 0;
+      foo(new RuntimeException());
+      e();
+    }
+  }
+
+  public void useExceptionCompliant() {
+    try {
+    } catch (Exception e) {
+      var x = e;
+    }
+
+    try {
+    } catch (Exception e) {
+      foo(e);
+    }
+
+    try {
+    } catch (Exception e) {
+      throw e;
+    }
+
+    try {
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+
+    try {
+    } catch (Exception e) {
+      var s = e.getStackTrace();
+    }
+
+    try {
+    } catch (Exception e) {
+      Supplier<Integer> s =() -> {
+        var x = e;
+        return 0;
+      };
+    }
+  }
+
+  public void foo(Exception e) {
+  }
+
+  public void e(){}
+}
diff --git a/java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21.java b/java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21.java
new file mode 100644
index 00000000000..6c221fc644a
--- /dev/null
+++ b/java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21.java
@@ -0,0 +1,19 @@
+package checks;
+
+import java.util.List;
+
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21 {
+  public void simpleUnUsedParameter() {
+
+    List<String> elements = List.of();
+    int value = 0;
+    try {
+      var elem = elements.get(10);
+      value = Integer.parseInt(elem);
+    } catch (NumberFormatException nfe) { // compliant java 21
+      System.err.println("Wrong number format");
+    } catch (IndexOutOfBoundsException ioob) { // compliant java 21
+      System.err.println("No such element");
+    }
+  }
+}
diff --git a/java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
new file mode 100644
index 00000000000..d61d6d9e4aa
--- /dev/null
+++ b/java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
@@ -0,0 +1,66 @@
+/*
+ * 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.checks;
+
+import java.util.List;
+import org.sonar.check.Rule;
+import org.sonar.java.checks.helpers.QuickFixHelper;
+import org.sonar.java.reporting.JavaQuickFix;
+import org.sonar.java.reporting.JavaTextEdit;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaVersion;
+import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
+import org.sonar.plugins.java.api.tree.CatchTree;
+import org.sonar.plugins.java.api.tree.IdentifierTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+
+@Rule(key = "S7467")
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
+
+  @Override
+  public List<Tree.Kind> nodesToVisit() {
+    return List.of(Tree.Kind.CATCH);
+  }
+
+  @Override
+  public void visitNode(Tree tree) {
+    CatchTree catchTree = (CatchTree) tree;
+    VariableTree v = catchTree.parameter();
+    IdentifierTree ident = v.simpleName();
+
+    if (!ident.isUnnamedVariable() && v.symbol().usages().isEmpty()) {
+      QuickFixHelper.newIssue(context)
+        .forRule(this)
+        .onTree(ident)
+        .withMessage(String.format("Replace \"%s\" with an unnamed pattern.", ident.name()))
+        .withQuickFix(() -> getQuickFix(ident))
+        .report();
+    }
+  }
+
+  @Override
+  public boolean isCompatibleWithJavaVersion(JavaVersion version) {
+    return version.isJava22Compatible();
+  }
+
+  private static JavaQuickFix getQuickFix(IdentifierTree ident) {
+    return JavaQuickFix.newQuickFix(String.format("Replace \"%s\" with unnamed pattern \"_\"", ident.name()))
+      .addTextEdit(JavaTextEdit.replaceTree(ident, "_"))
+      .build();
+  }
+}
diff --git a/java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java
new file mode 100644
index 00000000000..53d787a4106
--- /dev/null
+++ b/java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.checks;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+
+import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
+
+class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest {
+
+  @Test
+  void test_java22() {
+    CheckVerifier.newVerifier()
+      .onFile(mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withJavaVersion(22)
+      .verifyIssues();
+  }
+
+  @Test
+  void test_java21() {
+    CheckVerifier.newVerifier()
+      .onFile(mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withJavaVersion(21)
+      .verifyNoIssues();
+  }
+
+  @Test
+  void test_without_semantics() {
+    CheckVerifier.newVerifier()
+      .onFile(mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withoutSemantic()
+      .withJavaVersion(22)
+      .verifyIssues();
+  }
+
+}
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7467.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7467.html
new file mode 100644
index 00000000000..30bc57927d3
--- /dev/null
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7467.html
@@ -0,0 +1,40 @@
+<h2>Why is this an issue?</h2>
+<p>Good exception management is key to keeping a consistent application state in the face of errors and unexpected behaviors. However, in some cases,
+the information carried by the exception is not as important as the exception bubbling up itself. In such cases, developers may want to explicitly
+indicate that they have no use for the exception parameter. Java 22 introduces the unnamed variable pattern <code>_</code> which allows developers to
+free the catch clause from an unnecessary exception parameter name.</p>
+<h2>How to fix it</h2>
+<p>Replace exception parameter name with unnamed variable pattern <code>_</code>.</p>
+<h3>Code examples</h3>
+<h4>Noncompliant code example</h4>
+<pre data-diff-id="1" data-diff-type="noncompliant">
+List&lt;String&gt; elements = // ...
+int value = 0;
+try {
+  var elem = elements.get(idx);
+  value = Integer.parseInt(elem);
+} catch (NumberFormatException nfe) { // Noncompliant
+  System.err.println("Wrong number format");
+} catch (IndexOutOfBoundsException ioob) {  // Noncompliant
+  System.err.println("No such element");
+}
+</pre>
+<h4>Compliant solution</h4>
+<pre data-diff-id="1" data-diff-type="compliant">
+List&lt;String&gt; elements = // ...
+int value = 0;
+try {
+  var elem = elements.get(idx);
+  value = Integer.parseInt(elem);
+} catch (NumberFormatException _) {
+  System.err.println("Wrong number format");
+} catch (IndexOutOfBoundsException _) {
+  System.err.println("No such element");
+}
+</pre>
+<h2>Resources</h2>
+<h3>Documentation</h3>
+<ul>
+  <li> OpenJDK - <a href="https://openjdk.org/jeps/456">JEP 456: Unnamed Variables &amp; Patterns</a> </li>
+</ul>
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7467.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7467.json
new file mode 100644
index 00000000000..c39749e444a
--- /dev/null
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7467.json
@@ -0,0 +1,23 @@
+{
+  "title": "Unused exception parameter should use the unnamed variable pattern",
+  "type": "CODE_SMELL",
+  "status": "ready",
+  "remediation": {
+    "func": "Constant\/Issue",
+    "constantCost": "2min"
+  },
+  "tags": [
+    "java22"
+  ],
+  "defaultSeverity": "Minor",
+  "ruleSpecification": "RSPEC-7467",
+  "sqKey": "S7467",
+  "scope": "All",
+  "quickfix": "targeted",
+  "code": {
+    "impacts": {
+      "MAINTAINABILITY": "LOW"
+    },
+    "attribute": "CLEAR"
+  }
+}
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 462484eed54..eaf9318b534 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
@@ -504,6 +504,7 @@
     "S7190",
     "S7409",
     "S7435",
-    "S7466"
+    "S7466",
+    "S7467"
   ]
 }


Reviews:
user: login: tomasz-tylenda-sonarsource
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:10:52Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:21:39Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:23:10Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:24:21Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:24:52Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:33:16Z
user: login: erwan-serandour
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:33:38Z
user: login: tomasz-tylenda-sonarsource
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T07:17:42Z

Review comments:
diff_hunk: @@ -0,0 +1,52 @@
+/*
+ * 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.checks;
+
+import java.util.List;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaVersion;
+import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
+import org.sonar.plugins.java.api.tree.CatchTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+
+@Rule(key = "S7467")
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
+  private static final String UNNAMED_PATTERN = "_";
+
+  @Override
+  public List<Tree.Kind> nodesToVisit() {
+    return List.of(Tree.Kind.CATCH);
+  }
+
+  @Override
+  public void visitNode(Tree tree) {
+    CatchTree catchTree = (CatchTree) tree;
+    VariableTree v = catchTree.parameter();
+    String name = v.simpleName().name();
+    if (!UNNAMED_PATTERN.equals(name)
path: java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
user: login: tomasz-tylenda-sonarsource
body: `simpleName().isUnnamedVariable()` seems like a simpler way to check if it is unnamed.
created_at: 2025-04-16T12:42:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/*
+ * 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.checks;
+
+import java.util.List;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaVersion;
+import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
+import org.sonar.plugins.java.api.tree.CatchTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+
+@Rule(key = "S7467")
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
+  private static final String UNNAMED_PATTERN = "_";
+
+  @Override
+  public List<Tree.Kind> nodesToVisit() {
+    return List.of(Tree.Kind.CATCH);
+  }
+
+  @Override
+  public void visitNode(Tree tree) {
+    CatchTree catchTree = (CatchTree) tree;
+    VariableTree v = catchTree.parameter();
+    String name = v.simpleName().name();
+    if (!UNNAMED_PATTERN.equals(name)
+      && v.symbol().usages().isEmpty()) {
+      reportIssue(v.simpleName(), String.format("Replace %s with an unnamed pattern.", name));
path: java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
user: login: tomasz-tylenda-sonarsource
body: Let's add a quick fix right away as discuss in the standup today.
created_at: 2025-04-16T12:43:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,124 @@
+package checks;
+
+import io.restassured.exception.PathException;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample {
+  public void simpleNonCompliant() {
+    try {
+    } catch (Exception e) {
+      var x = e;
+    }
+
+    List<String> elements = List.of();
+    int value = 0;
+    try {
+      var elem = elements.get(10);
+      value = Integer.parseInt(elem);
+    } catch (NumberFormatException nfe) { // Noncompliant {{Replace nfe with an unnamed pattern.}}
path: java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java
user: login: tomasz-tylenda-sonarsource
body: Let's quote variable names (`"Replace \"nfe\" with..."`).
created_at: 2025-04-16T12:45:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/*
+ * 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.checks;
+
+import java.util.List;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaVersion;
+import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
+import org.sonar.plugins.java.api.tree.CatchTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+
+@Rule(key = "S7467")
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
+  private static final String UNNAMED_PATTERN = "_";
+
+  @Override
+  public List<Tree.Kind> nodesToVisit() {
+    return List.of(Tree.Kind.CATCH);
+  }
+
+  @Override
+  public void visitNode(Tree tree) {
+    CatchTree catchTree = (CatchTree) tree;
+    VariableTree v = catchTree.parameter();
+    String name = v.simpleName().name();
+    if (!UNNAMED_PATTERN.equals(name)
+      && v.symbol().usages().isEmpty()) {
path: java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
user: login: tomasz-tylenda-sonarsource
body: There's no need to break the line in such a short condition.
created_at: 2025-04-16T12:49:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+/*
+ * 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.checks;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+import org.sonar.java.checks.verifier.TestUtils;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest {
+
+  @Test
+  void test_java22() {
+    CheckVerifier.newVerifier()
+      .onFile(TestUtils.mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
path: java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java
user: login: tomasz-tylenda-sonarsource
body: `mainCodeSourcesPath` usually uses `import static`. Let's do that instead.
created_at: 2025-04-16T12:53:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+/*
+ * 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.checks;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+import org.sonar.java.checks.verifier.TestUtils;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest {
+
+  @Test
+  void test_java22() {
+    CheckVerifier.newVerifier()
+      .onFile(TestUtils.mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withJavaVersion(22)
+      .verifyIssues();
+  }
+
+  @Test
+  void test_java21() {
+    var verifier = CheckVerifier.newVerifier()
+      .onFile(TestUtils.mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withJavaVersion(21);
+
+    assertThatThrownBy(verifier::verifyNoIssues)
path: java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java
user: login: tomasz-tylenda-sonarsource
body: I'm not a fan of checking for AssertionError. Is there an existing occurence somewhere in some test of some check? All it does is confirming, that we cannot parse Java 22 when we set the version to 21. There is another location for non-compiling sources. How about copying this input (maybe in a simplified form) and testing on that?
created_at: 2025-04-16T12:56:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,124 @@
+package checks;
+
+import io.restassured.exception.PathException;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample {
+  public void simpleNonCompliant() {
+    try {
+    } catch (Exception e) {
path: java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java
user: login: tomasz-tylenda-sonarsource
body: Isn't it tested in `useExceptionCompliant()`? Can we remove it?
created_at: 2025-04-16T13:08:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/*
+ * 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.checks;
+
+import java.util.List;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaVersion;
+import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
+import org.sonar.plugins.java.api.tree.CatchTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+
+@Rule(key = "S7467")
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
+  private static final String UNNAMED_PATTERN = "_";
+
+  @Override
+  public List<Tree.Kind> nodesToVisit() {
+    return List.of(Tree.Kind.CATCH);
+  }
+
+  @Override
+  public void visitNode(Tree tree) {
+    CatchTree catchTree = (CatchTree) tree;
+    VariableTree v = catchTree.parameter();
+    String name = v.simpleName().name();
+    if (!UNNAMED_PATTERN.equals(name)
path: java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
user: login: erwan-serandour
body: good catch :+1: 
created_at: 2025-04-16T13:21:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,124 @@
+package checks;
+
+import io.restassured.exception.PathException;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample {
+  public void simpleNonCompliant() {
+    try {
+    } catch (Exception e) {
path: java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java
user: login: erwan-serandour
body: it was a mistake, I have removed it
created_at: 2025-04-16T13:23:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/*
+ * 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.checks;
+
+import java.util.List;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaVersion;
+import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
+import org.sonar.plugins.java.api.tree.CatchTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+
+@Rule(key = "S7467")
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
+  private static final String UNNAMED_PATTERN = "_";
+
+  @Override
+  public List<Tree.Kind> nodesToVisit() {
+    return List.of(Tree.Kind.CATCH);
+  }
+
+  @Override
+  public void visitNode(Tree tree) {
+    CatchTree catchTree = (CatchTree) tree;
+    VariableTree v = catchTree.parameter();
+    String name = v.simpleName().name();
+    if (!UNNAMED_PATTERN.equals(name)
+      && v.symbol().usages().isEmpty()) {
path: java-checks/src/main/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheck.java
user: login: erwan-serandour
body: :+1: 
created_at: 2025-04-16T13:24:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+/*
+ * 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.checks;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+import org.sonar.java.checks.verifier.TestUtils;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest {
+
+  @Test
+  void test_java22() {
+    CheckVerifier.newVerifier()
+      .onFile(TestUtils.mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
path: java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java
user: login: erwan-serandour
body: :+1: 
created_at: 2025-04-16T13:24:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+/*
+ * 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.checks;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+import org.sonar.java.checks.verifier.TestUtils;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest {
+
+  @Test
+  void test_java22() {
+    CheckVerifier.newVerifier()
+      .onFile(TestUtils.mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withJavaVersion(22)
+      .verifyIssues();
+  }
+
+  @Test
+  void test_java21() {
+    var verifier = CheckVerifier.newVerifier()
+      .onFile(TestUtils.mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
+      .withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
+      .withJavaVersion(21);
+
+    assertThatThrownBy(verifier::verifyNoIssues)
path: java-checks/src/test/java/org/sonar/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest.java
user: login: erwan-serandour
body: indeed you right, I have created a new file only with valid java 21 code to verify we do not raise.
created_at: 2025-04-16T13:33:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,124 @@
+package checks;
+
+import io.restassured.exception.PathException;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample {
+  public void simpleNonCompliant() {
+    try {
+    } catch (Exception e) {
+      var x = e;
+    }
+
+    List<String> elements = List.of();
+    int value = 0;
+    try {
+      var elem = elements.get(10);
+      value = Integer.parseInt(elem);
+    } catch (NumberFormatException nfe) { // Noncompliant {{Replace nfe with an unnamed pattern.}}
path: java-checks-test-sources/default/src/main/java/checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java
user: login: erwan-serandour
body: :+1: 
created_at: 2025-04-16T13:33:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqube-next[bot]
created_at: 2025-04-17T07:32:17Z
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=5087) **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=5087&resolved=false) [0 New issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5087&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=5087) [0 Fixed issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&fixedInPullRequest=5087)  
[![](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=5087&issueStatuses=ACCEPTED) [0 Accepted issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.java%3Ajava&pullRequest=5087&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=5087) [0 Security Hotspots](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.java%3Ajava&pullRequest=5087)  
[![](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=5087&metric=new_coverage&view=list) [100.0% Coverage on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.java%3Ajava&pullRequest=5087&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=5087&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=5087&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.java%3Ajava&pullRequest=5087)


author_association: NONE