Repository: checkstyle/checkstyle

Problem statement:
Add Check Support for Java 21 Unnamed Variables & Patterns Syntax: New Check UnusedCatchParameterShouldBeUnnamed
child of #14942 
violate the non-use of the catch parameters. They should be unnamed.

---

**Config**

```XML
$ cat config.xml 
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <module name="TreeWalker">
        <module name="UnusedCatchParameterShouldBeUnnamed"/>
    </module>
</module>
```

---

**Example**

```Java
 public class Test {
    void test(Object obj) {
        try {
            int x = 1 / 0;
        } catch (Exception e) {   // violation
            System.out.println("error");
        }
        try {
            int x = 1 / 0;
        } catch (Exception _) {
            System.out.println("error");
        }
    }
}
```

---

**Expected Output**

```PowerShell
$ java -jar checkstyle-10.19.0-all.jar -c config.xml Test.java 
Starting audit...
[ERROR] D:\Test.java:5:28:Unused catch parameter should be unnamed [UnusedCatchParameterShouldBeUnnamed]
Audit done.
Checkstyle ends with 1 errors.
```

>How we could suppress violations in this check for (ignore|ignored) naming convention

https://github.com/checkstyle/checkstyle/issues/15058#issuecomment-2254523431


Hints:
I am good with this.

> `} catch (Exception e) {   // violation`
> `} catch (Exception _) {`

Technically 2nd is "expected violation". 1st should be violation of old check? This is a new check and there are no actual violations yet.
> Technically 2nd is "expected violation". 1st should be violation of old check? This is a new check and there are no actual violations yet.

can you restate this I don't understand. but for now `UnusedLocalVariable` doesn't validate the catch parameters. this new check will violate the non-use of the catch parameters and require them to be unnamed. ( and we will not violate the unnamed parameters `_`)
Sorry, I was trying to convey that calling this a violation but have it listed in the "expected output" didn't seem right, but I think I am just being overly picky.

My comment can be ignored, I am still good with this issue.

Patch:
diff --git a/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionUnusedCatchParameterShouldBeUnnamedTest.java b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionUnusedCatchParameterShouldBeUnnamedTest.java
new file mode 100644
--- /dev/null
+++ b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionUnusedCatchParameterShouldBeUnnamedTest.java
@@ -0,0 +1,122 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.checkstyle.suppressionxpathfilter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
+import com.puppycrawl.tools.checkstyle.checks.coding.UnusedCatchParameterShouldBeUnnamedCheck;
+
+public class XpathRegressionUnusedCatchParameterShouldBeUnnamedTest
+        extends AbstractXpathTestSupport {
+
+    private final String checkName = UnusedCatchParameterShouldBeUnnamedCheck.class
+                                                .getSimpleName();
+
+    @Override
+    protected String getCheckName() {
+        return checkName;
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        final File fileToProcess =
+                new File(getNonCompilablePath(
+                        "InputXpathUnusedCatchParameterShouldBeUnnamedSimple.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(UnusedCatchParameterShouldBeUnnamedCheck.class);
+
+        final String[] expectedViolation = {
+            "10:16: " + getCheckMessage(UnusedCatchParameterShouldBeUnnamedCheck.class,
+                    UnusedCatchParameterShouldBeUnnamedCheck.MSG_UNUSED_CATCH_PARAMETER,
+                    "e"),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                        + "[@text='InputXpathUnusedCatchParameterShouldBeUnnamedSimple']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY/"
+                        + "LITERAL_CATCH/PARAMETER_DEF[./IDENT[@text='e']]",
+                "/COMPILATION_UNIT/CLASS_DEF["
+                        + "./IDENT[@text='InputXpathUnusedCatchParameterShouldBeUnnamedSimple']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/"
+                        + "LITERAL_TRY/LITERAL_CATCH/PARAMETER_DEF[./IDENT[@text='e']]/MODIFIERS",
+                "/COMPILATION_UNIT/CLASS_DEF["
+                        + "./IDENT[@text='InputXpathUnusedCatchParameterShouldBeUnnamedSimple']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY/"
+                        + "LITERAL_CATCH/PARAMETER_DEF[./IDENT[@text='e']]"
+                        + "/TYPE[./IDENT[@text='Exception']]",
+                "/COMPILATION_UNIT/CLASS_DEF"
+                        + "[./IDENT[@text='InputXpathUnusedCatchParameterShouldBeUnnamedSimple']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY"
+                        + "/LITERAL_CATCH/PARAMETER_DEF[./IDENT[@text='e']]"
+                        + "/TYPE/IDENT[@text='Exception']"
+        );
+
+        runVerifications(moduleConfig, fileToProcess, expectedViolation,
+                expectedXpathQueries);
+    }
+
+    @Test
+    public void testNested() throws Exception {
+        final File fileToProcess =
+                new File(getNonCompilablePath(
+                        "InputXpathUnusedCatchParameterShouldBeUnnamedNested.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(UnusedCatchParameterShouldBeUnnamedCheck.class);
+
+        final String[] expectedViolation = {
+            "14:20: " + getCheckMessage(UnusedCatchParameterShouldBeUnnamedCheck.class,
+                    UnusedCatchParameterShouldBeUnnamedCheck.MSG_UNUSED_CATCH_PARAMETER,
+                    "exception"),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF["
+                        + "./IDENT[@text='InputXpathUnusedCatchParameterShouldBeUnnamedNested']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY/"
+                        + "LITERAL_CATCH/SLIST/LITERAL_TRY/LITERAL_CATCH/PARAMETER_DEF"
+                        + "[./IDENT[@text='exception']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+                        + "[@text='InputXpathUnusedCatchParameterShouldBeUnnamedNested']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY"
+                        + "/LITERAL_CATCH/SLIST/LITERAL_TRY/LITERAL_CATCH/PARAMETER_DEF["
+                        + "./IDENT[@text='exception']]/MODIFIERS",
+                "/COMPILATION_UNIT/CLASS_DEF"
+                        + "[./IDENT[@text='InputXpathUnusedCatchParameterShouldBeUnnamedNested']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY/"
+                        + "LITERAL_CATCH/SLIST/LITERAL_TRY/LITERAL_CATCH/PARAMETER_DEF"
+                        + "[./IDENT[@text='exception']]/TYPE[./IDENT[@text='Exception']]",
+                "/COMPILATION_UNIT/CLASS_DEF"
+                        + "[./IDENT[@text='InputXpathUnusedCatchParameterShouldBeUnnamedNested']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_TRY"
+                        + "/LITERAL_CATCH/SLIST/LITERAL_TRY/LITERAL_CATCH/PARAMETER_DEF"
+                        + "[./IDENT[@text='exception']]/TYPE/IDENT[@text='Exception']"
+        );
+        runVerifications(moduleConfig, fileToProcess, expectedViolation,
+                expectedXpathQueries);
+    }
+}
diff --git a/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/unusedcatchparametershouldbeunnamed/InputXpathUnusedCatchParameterShouldBeUnnamedNested.java b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/unusedcatchparametershouldbeunnamed/InputXpathUnusedCatchParameterShouldBeUnnamedNested.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/unusedcatchparametershouldbeunnamed/InputXpathUnusedCatchParameterShouldBeUnnamedNested.java
@@ -0,0 +1,20 @@
+//non-compiled with javac: Compilable with Java21
+package org.checkstyle.suppressionxpathfilter.unusedcatchparametershouldbeunnamed;
+
+public class InputXpathUnusedCatchParameterShouldBeUnnamedNested {
+
+    void test() {
+        try {
+            int x = 1 / 0;
+        }
+        catch (Exception e) {
+            try {
+                int z = 5 / 0;
+            }
+            catch (Exception exception) {  // warn
+                System.out.println("infinity");
+            }
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/unusedcatchparametershouldbeunnamed/InputXpathUnusedCatchParameterShouldBeUnnamedSimple.java b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/unusedcatchparametershouldbeunnamed/InputXpathUnusedCatchParameterShouldBeUnnamedSimple.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/unusedcatchparametershouldbeunnamed/InputXpathUnusedCatchParameterShouldBeUnnamedSimple.java
@@ -0,0 +1,14 @@
+//non-compiled with javac: Compilable with Java21
+package org.checkstyle.suppressionxpathfilter.unusedcatchparametershouldbeunnamed;
+
+public class InputXpathUnusedCatchParameterShouldBeUnnamedSimple {
+
+    void test() {
+        try {
+            int x = 1 / 0;
+        }
+        catch (Exception e) {  // warn
+            System.out.println("infinity");
+        }
+    }
+}
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java b/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java
@@ -601,6 +601,8 @@ private static void fillChecksFromCodingPackage() {
                 BASE_PACKAGE + ".checks.coding.MatchXpathCheck");
         NAME_TO_FULL_MODULE_NAME.put("UnusedLocalVariableCheck",
                 BASE_PACKAGE + ".checks.coding.UnusedLocalVariableCheck");
+        NAME_TO_FULL_MODULE_NAME.put("UnusedCatchParameterShouldBeUnnamedCheck",
+                BASE_PACKAGE + ".checks.coding.UnusedCatchParameterShouldBeUnnamedCheck");
     }
 
     /**
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheck.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheck.java
@@ -0,0 +1,263 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Optional;
+
+import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
+
+/**
+ * <p>
+ * Ensures that catch parameters that are not used are declared as an unnamed variable.
+ * </p>
+ * <p>
+ * Rationale:
+ * </p>
+ * <ul>
+ *     <li>
+ *         Improves code readability by clearly indicating which parameters are unused.
+ *     </li>
+ *     <li>
+ *         Follows Java conventions for denoting unused parameters with an underscore ({@code _}).
+ *     </li>
+ * </ul>
+ * <p>
+ * See the <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
+ * Java Language Specification</a> for more information about unnamed variables.
+ * </p>
+ * <p>
+ * <b>Attention</b>: This check should be activated only on source code
+ * that is compiled by jdk21 or higher;
+ * unnamed catch parameters came out as the first preview in Java 21.
+ * </p>
+ * <p>
+ * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
+ * </p>
+ * <p>
+ * Violation Message Keys:
+ * </p>
+ * <ul>
+ * <li>
+ * {@code unused.catch.parameter}
+ * </li>
+ * </ul>
+ *
+ * @since 10.18.0
+ *
+ */
+
+@FileStatefulCheck
+public class UnusedCatchParameterShouldBeUnnamedCheck extends AbstractCheck {
+
+    /**
+     * A key is pointing to the warning message text in "messages.properties"
+     * file.
+     */
+    public static final String MSG_UNUSED_CATCH_PARAMETER = "unused.catch.parameter";
+
+    /**
+     * Invalid parents of the catch parameter identifier.
+     */
+    private static final int[] INVALID_CATCH_PARAM_IDENT_PARENTS = {
+        TokenTypes.DOT,
+        TokenTypes.LITERAL_NEW,
+        TokenTypes.METHOD_CALL,
+        TokenTypes.TYPE,
+    };
+
+    /**
+     * Keeps track of the catch parameters in a block.
+     */
+    private final Deque<CatchParameterDetails> catchParameters = new ArrayDeque<>();
+
+    @Override
+    public int[] getDefaultTokens() {
+        return getRequiredTokens();
+    }
+
+    @Override
+    public int[] getAcceptableTokens() {
+        return getRequiredTokens();
+    }
+
+    @Override
+    public int[] getRequiredTokens() {
+        return new int[] {
+            TokenTypes.LITERAL_CATCH,
+            TokenTypes.IDENT,
+        };
+    }
+
+    @Override
+    public void beginTree(DetailAST rootAST) {
+        catchParameters.clear();
+    }
+
+    @Override
+    public void visitToken(DetailAST ast) {
+        if (ast.getType() == TokenTypes.LITERAL_CATCH) {
+            final CatchParameterDetails catchParameter = new CatchParameterDetails(ast);
+            catchParameters.push(catchParameter);
+        }
+        else if (isCatchParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) {
+            // we do not count reassignment as usage
+            catchParameters.stream()
+                    .filter(parameter -> parameter.getName().equals(ast.getText()))
+                    .findFirst()
+                    .ifPresent(CatchParameterDetails::registerAsUsed);
+        }
+    }
+
+    @Override
+    public void leaveToken(DetailAST ast) {
+        if (ast.getType() == TokenTypes.LITERAL_CATCH) {
+            final Optional<CatchParameterDetails> unusedCatchParameter =
+                    Optional.ofNullable(catchParameters.peek())
+                            .filter(parameter -> !parameter.isUsed())
+                            .filter(parameter -> !"_".equals(parameter.getName()));
+
+            unusedCatchParameter.ifPresent(parameter -> {
+                log(parameter.getParameterDefinition(),
+                        MSG_UNUSED_CATCH_PARAMETER,
+                        parameter.getName());
+            });
+            catchParameters.pop();
+        }
+    }
+
+    /**
+     * Visit ast of type {@link TokenTypes#IDENT}
+     * and check if it is a candidate for a catch parameter identifier.
+     *
+     * @param identifierAst token representing {@link TokenTypes#IDENT}
+     * @return true if the given {@link TokenTypes#IDENT} could be a catch parameter identifier
+     */
+    private static boolean isCatchParameterIdentifierCandidate(DetailAST identifierAst) {
+        // we should ignore the ident if it is in the exception declaration
+        final boolean isCatchParameterDeclaration =
+                identifierAst.getParent().getParent().getType() == TokenTypes.LITERAL_CATCH;
+
+        final boolean hasValidParentToken =
+                !TokenUtil.isOfType(identifierAst.getParent(), INVALID_CATCH_PARAM_IDENT_PARENTS);
+
+        final boolean isMethodInvocation = isMethodInvocation(identifierAst);
+
+        return !isCatchParameterDeclaration && (hasValidParentToken || isMethodInvocation);
+    }
+
+    /**
+     * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator
+     * and is a candidate for catch parameter.
+     *
+     * @param identAst token representing {@link TokenTypes#IDENT}
+     * @return true if the given {@link TokenTypes#IDENT} is a child of a dot operator
+     *     and a candidate for catch parameter.
+     */
+    private static boolean isMethodInvocation(DetailAST identAst) {
+        final DetailAST parent = identAst.getParent();
+        return parent.getType() == TokenTypes.DOT
+                && identAst.equals(parent.getFirstChild());
+    }
+
+    /**
+     * Check if the given {@link TokenTypes#IDENT} is a left hand side value.
+     *
+     * @param identAst token representing {@link TokenTypes#IDENT}
+     * @return true if the given {@link TokenTypes#IDENT} is a left hand side value.
+     */
+    private static boolean isLeftHandOfAssignment(DetailAST identAst) {
+        final DetailAST parent = identAst.getParent();
+        return parent.getType() == TokenTypes.ASSIGN
+                && !identAst.equals(parent.getLastChild());
+    }
+
+    /**
+     * Maintains information about the catch parameter.
+     */
+    private static final class CatchParameterDetails {
+
+        /**
+         * The name of the catch parameter.
+         */
+        private final String name;
+
+        /**
+         * Ast of type {@link TokenTypes#PARAMETER_DEF} to use it when logging.
+         */
+        private final DetailAST parameterDefinition;
+
+        /**
+         * Is the variable used.
+         */
+        private boolean used;
+
+        /**
+         * Create a new catch parameter instance.
+         *
+         * @param enclosingCatchClause ast of type {@link TokenTypes#LITERAL_CATCH}
+         */
+        private CatchParameterDetails(DetailAST enclosingCatchClause) {
+            parameterDefinition =
+                    enclosingCatchClause.findFirstToken(TokenTypes.PARAMETER_DEF);
+            name = parameterDefinition.findFirstToken(TokenTypes.IDENT).getText();
+        }
+
+        /**
+         * Register the catch parameter as used.
+         */
+        private void registerAsUsed() {
+            used = true;
+        }
+
+        /**
+         * Get the name of the catch parameter.
+         *
+         * @return the name of the catch parameter
+         */
+        private String getName() {
+            return name;
+        }
+
+        /**
+         * Check if the catch parameter is used.
+         *
+         * @return true if the catch parameter is used
+         */
+        private boolean isUsed() {
+            return used;
+        }
+
+        /**
+         * Get the parameter definition token of the catch parameter
+         * represented by ast of type {@link TokenTypes#PARAMETER_DEF}.
+         *
+         * @return the ast of type {@link TokenTypes#PARAMETER_DEF}
+         */
+        private DetailAST getParameterDefinition() {
+            return parameterDefinition;
+        }
+    }
+}
diff --git a/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheckExamplesTest.java b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheckExamplesTest.java
new file mode 100644
--- /dev/null
+++ b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheckExamplesTest.java
@@ -0,0 +1,46 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import static com.puppycrawl.tools.checkstyle.checks.coding.UnusedCatchParameterShouldBeUnnamedCheck.MSG_UNUSED_CATCH_PARAMETER;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractExamplesModuleTestSupport;
+
+public class UnusedCatchParameterShouldBeUnnamedCheckExamplesTest extends
+        AbstractExamplesModuleTestSupport {
+
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed";
+    }
+
+    @Test
+    public void testExample1() throws Exception {
+        final String[] expected = {
+            "20:14: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "exception"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("Example1.java"),
+                expected);
+    }
+
+}
diff --git a/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/Example1.java b/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/Example1.java
new file mode 100644
--- /dev/null
+++ b/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/Example1.java
@@ -0,0 +1,40 @@
+/*xml
+<module name="Checker">
+  <module name="TreeWalker">
+    <module name="UnusedCatchParameterShouldBeUnnamed"/>
+  </module>
+</module>
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedcatchparametershouldbeunnamed;
+
+// xdoc section -- start
+class Example1 {
+
+  void test() {
+
+    try {
+      int x = 1 / 0;
+      // violation below, 'Unused catch parameter 'exception' should be unnamed'
+    } catch (Exception exception) {
+      System.out.println("infinity");
+    }
+
+    try {
+      int x = 1 / 0;
+      // ok below, 'declared as unnamed parameter'
+    } catch (Exception _) {
+      System.out.println("infinity");
+    }
+
+    try {
+      int x = 1 / 0;
+      // ok below, ''exception' is used'
+    } catch (Exception exception) {
+      System.out.println("Got Exception - " +  exception.getMessage());
+    }
+
+  }
+}
+// xdoc section -- end


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheckTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedCatchParameterShouldBeUnnamedCheckTest.java
@@ -0,0 +1,148 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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 GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.puppycrawl.tools.checkstyle.checks.coding.UnusedCatchParameterShouldBeUnnamedCheck.MSG_UNUSED_CATCH_PARAMETER;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
+import com.puppycrawl.tools.checkstyle.JavaParser;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
+
+public class UnusedCatchParameterShouldBeUnnamedCheckTest extends AbstractModuleTestSupport {
+
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed";
+    }
+
+    @Test
+    public void testGetRequiredTokens() {
+        final UnusedCatchParameterShouldBeUnnamedCheck checkObj =
+                            new UnusedCatchParameterShouldBeUnnamedCheck();
+        final int[] expected = {
+            TokenTypes.LITERAL_CATCH,
+            TokenTypes.IDENT,
+        };
+        assertWithMessage("Default required tokens are invalid")
+            .that(checkObj.getRequiredTokens())
+            .isEqualTo(expected);
+        assertWithMessage("Default acceptable tokens are invalid")
+            .that(checkObj.getAcceptableTokens())
+            .isEqualTo(expected);
+        assertWithMessage("Default tokens are invalid")
+            .that(checkObj.getDefaultTokens())
+            .isEqualTo(expected);
+    }
+
+    @Test
+    public void testUnusedCatchParameterShouldBeUnnamed() throws Exception {
+        final String[] expected = {
+            "17:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "24:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "31:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "52:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "65:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "A"),
+            "72:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "A"),
+            "79:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "103:16: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputUnusedCatchParameterShouldBeUnnamed.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedCatchParameterShouldBeUnnamedWithResourceAndFinally() throws Exception {
+        final String[] expected = {
+            "19:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "38:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "48:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "69:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath(
+                        "InputUnusedCatchParameterShouldBeUnnamedWithResourceAndFinally.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedCatchParameterShouldBeUnnamedNested() throws Exception {
+        final String[] expected = {
+            "15:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "18:22: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "ex"),
+            "31:22: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "ex"),
+            "42:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "87:22: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "ex"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath(
+                        "InputUnusedCatchParameterShouldBeUnnamedNested.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedCatchParameterShouldBeUnnamedInsideAnonClass() throws Exception {
+        final String[] expected = {
+            "14:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "39:28: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "50:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "57:28: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+            "92:30: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "ex"),
+            "103:18: " + getCheckMessage(MSG_UNUSED_CATCH_PARAMETER, "e"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath(
+                        "InputUnusedCatchParameterShouldBeUnnamedInsideAnonClass.java"),
+                expected);
+    }
+
+    @Test
+    public void testClearState() throws Exception {
+        final UnusedCatchParameterShouldBeUnnamedCheck check =
+                new UnusedCatchParameterShouldBeUnnamedCheck();
+
+        final DetailAST root = JavaParser.parseFile(
+                new File(getNonCompilablePath(
+                        "InputUnusedCatchParameterShouldBeUnnamed.java")),
+                JavaParser.Options.WITHOUT_COMMENTS);
+
+        final Optional<DetailAST> literalCatch = TestUtil.findTokenInAstByPredicate(root,
+            ast -> ast.getType() == TokenTypes.LITERAL_CATCH);
+
+        assertWithMessage("State is not cleared on beginTree")
+                .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check,
+                        literalCatch.orElseThrow(),
+                        "catchParameters",
+                        catchParameters -> {
+                            return ((Collection<?>) catchParameters).isEmpty();
+                        }))
+                .isTrue();
+    }
+
+}
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
@@ -40,20 +40,20 @@ protected String getPackageLocation() {
 
     @Test
     public void test() {
-        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(205);
+        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(206);
     }
 
     @Test
     public void testDuplicatePackage() {
         assertThat(XmlMetaReader
                     .readAllModulesIncludingThirdPartyIfAny("com.puppycrawl.tools.checkstyle.meta"))
-                .hasSize(205);
+                .hasSize(206);
     }
 
     @Test
     public void testBadPackage() {
         assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny("DOES.NOT.EXIST"))
-                .hasSize(205);
+                .hasSize(206);
     }
 
     @Test
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamed.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamed.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamed.java
@@ -0,0 +1,118 @@
+/*
+UnusedCatchParameterShouldBeUnnamed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedcatchparametershouldbeunnamed;
+
+public class InputUnusedCatchParameterShouldBeUnnamed {
+    private E e;
+    Exception exception;
+
+    void test(Object o) {
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) { // violation
+            System.out.println("infinity");
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (final Exception e) {  // violation
+
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (@SuppressWarnings("") Exception e) {  // violation
+            this.e.printStackTrace();
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {
+            System.out.println(e);
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {  // violation
+           e = new Exception();
+        }
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {
+            exception = e;
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception A) {  // violation
+           A a;
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception A) {  // violation
+           Object a = new A();
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {   // violation
+            System.out.println(this.e);
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {
+           try { int y = 1/0; }
+           catch (Exception _) {
+               System.out.println(e.getLocalizedMessage());
+           }
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {
+            test(e);
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception e) {  // violation
+            e();
+        }
+
+        try {
+            int x = 1/ 0;
+        }
+        catch (Exception _) {
+            System.out.println("infinity");
+        }
+    }
+    void e() { }
+}
+
+class E { void printStackTrace() {}}
+class A {}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedInsideAnonClass.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedInsideAnonClass.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedInsideAnonClass.java
@@ -0,0 +1,116 @@
+/*
+UnusedCatchParameterShouldBeUnnamed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedcatchparametershouldbeunnamed;
+
+public class InputUnusedCatchParameterShouldBeUnnamedInsideAnonClass {
+
+    void testNestedInsideAnonClass() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) {  // violation
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        int x = 1 / 0;
+                    } catch (Exception e) {
+                        System.out.println(e.getMessage());
+                    }
+                }
+            };
+        }
+    }
+
+    void testNestedInsideAnonClass2() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        int x = 1 / 0;
+                        throw e;
+                    }
+                    catch (Exception e) {  // violation
+
+                    }
+                }
+            };
+        }
+    }
+
+    void testNestedInsideAnonClass3() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) { // violation
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        int x = 1 / 0;
+                    }
+                    catch (Exception e) { // violation
+
+                    }
+                }
+            };
+        }
+    }
+
+    void testNestedInsideAnonClass4() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        int x = 1 / 0;
+                    } catch (Exception e) {
+                        System.out.println(e.getMessage());
+                    }
+                    System.out.println(e.getMessage());
+                }
+            };
+        }
+    }
+
+    void testNestedInsideAnonClass5() {
+        try {
+            int x = 1 / 0;
+        } catch (final Exception e) {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        int x = 1 / 0;
+                    } catch (Exception ex) { // violation
+                        System.out.println(e.getMessage());
+                    }
+                }
+            };
+        }
+    }
+
+    void testNestedInsideAnonClass6() {
+        try {
+            int x = 1 / 0;
+        } catch (final Exception e) { // violation
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        int x = 1 / 0;
+                    } catch (Exception ex) {
+                        System.out.println(ex.getMessage());
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedNested.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedNested.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedNested.java
@@ -0,0 +1,100 @@
+/*
+UnusedCatchParameterShouldBeUnnamed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedcatchparametershouldbeunnamed;
+
+public class InputUnusedCatchParameterShouldBeUnnamedNested {
+
+
+    void testNestedCatch1() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) { // violation
+            try {
+                int y = 1 / 0;
+            } catch (Exception ex) {  // violation
+                System.out.println("infinity");
+            }
+            System.out.println("infinity");
+        }
+    }
+
+    void testNestedCatch2() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) {
+            try {
+                int y = 1 / 0;
+            } catch (Exception ex) {  // violation
+                e.printStackTrace();
+                System.out.println("infinity");
+            }
+            System.out.println("infinity");
+        }
+    }
+
+    void testNestedCatch3() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) { // violation
+            try {
+                int y = 1 / 0;
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                System.out.println("infinity");
+            }
+            System.out.println("infinity");
+        }
+    }
+
+    void testNestedCatch4() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) {
+            try {
+                int y = 1 / 0;
+                e.printStackTrace();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                System.out.println("infinity");
+            }
+            System.out.println("infinity");
+        }
+    }
+
+    void testNestedCatch5() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception _) {
+            try {
+                int y = 1 / 0;
+            } catch (Exception _) {
+                System.out.println("infinity");
+            }
+            System.out.println("infinity");
+        }
+    }
+
+    void testNestedCatchTwoLevels() {
+        try {
+            int x = 1 / 0;
+        } catch (Exception e) {
+            try {
+                int y = 1 / 0;
+            } catch (Exception ex) { // violation
+                try {
+                    int z = 1 / 0;
+                } catch (Exception exx) {
+                    exx.printStackTrace();
+                    System.out.println("infinity");
+                }
+                e.printStackTrace();
+                System.out.println("infinity");
+            }
+            System.out.println("infinity");
+        }
+    }
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedWithResourceAndFinally.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedWithResourceAndFinally.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedcatchparametershouldbeunnamed/InputUnusedCatchParameterShouldBeUnnamedWithResourceAndFinally.java
@@ -0,0 +1,98 @@
+/*
+UnusedCatchParameterShouldBeUnnamed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedcatchparametershouldbeunnamed;
+
+import java.io.File;
+
+public class InputUnusedCatchParameterShouldBeUnnamedWithResourceAndFinally {
+    private E e;
+    Exception exception;
+
+    void testMultiCatch() {
+        try {
+            int y = 5;
+            File file = new File("file.txt");
+        } catch (NullPointerException | IllegalArgumentException e) { // violation
+            this.e.printStackTrace();
+        }
+
+        try {
+            int y = 5;
+            File file = new File("file.txt");
+        } catch (NullPointerException | IllegalArgumentException _) {
+            this.e.printStackTrace();
+        }
+
+        try {
+            File file = new File("file.txt");
+        } catch (NullPointerException | IllegalArgumentException e) {
+            e.printStackTrace();
+        }
+
+        try {
+            File file = new File("file.txt");
+        } catch (NullPointerException e) { // violation
+            System.out.println("null pointer exception");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    void testTryWithResource() {
+        try (var a = lock()) {
+            int y = 5;
+        } catch (Exception e) {  // violation
+            // no code
+        }
+
+        try (var a = lock()) {
+            int y = 5;
+        } catch (Exception e) {
+            System.out.println(e.toString());
+        }
+
+        try (var a = lock()) {
+            int y = 5;
+        } catch (Exception _) {
+            System.out.println(e.toString());
+        }
+
+    }
+
+    void tryCatchFinally() {
+        try {
+            File file = new File("file.txt");
+        } catch (NullPointerException | IllegalArgumentException e) { // violation
+            this.e.printStackTrace();
+        } finally {
+            System.out.println(e); // catch parameter is not in this scope
+        }
+
+        try {
+            File file = new File("file.txt");
+        } catch (NullPointerException | IllegalArgumentException e) {
+            e.printStackTrace();
+        } finally {
+            System.out.println("close the file");
+        }
+    }
+
+    void e() {
+    }
+
+    AutoCloseable lock() {
+        return null;
+    }
+}
+
+class E {
+    void printStackTrace() {
+    }
+}
+
+class A {
+}