Repository: checkstyle/checkstyle

Problem statement:
New Check: UnusedVariable
I'd like to request an unused variable module. The following should fail:

    public void foo() {
        Integer bar = 1;
        // This should fail since bar is not used
    }
    
Where as:

    public Integer foo() {
        Integer bar = 1;
        return bar
    }
    
Should not since it is used.

Thanks



Hints:
Agree, this would be useful.
Hello, I would like to work on this issue.
@eriklansner-blip If you are new to checkstyle then I would not recommend this issue as a first time. To be able to see if a variable is used or not would require some degree of tracking the variable in the code. There could be name conflicts like a field with the same name, or an anonymous class. It may require some degree of difficulty like the `RequireThisCheck`.
We are welcoming you to make a fix, we will guide you on how to make it and we will assist you.

We just try to warn you in advance that it might be not that easy when it comes to edge cases.
Thank you for the responses. I am part of a student group that has been tasked with resolving an issue on an open-source project. We chose this particular issue and we understand that it will not be that easy. We will give it some amount of hours and we will get back to you with the results.
sadly we did not solve the issue of shadowing and stopped working on it.
I am on it
I think that this check should verify usage of pattern variables, since they are just a special case of local variable. Example:

```bash
public class Test {
    void method1(Object o) {
        if (o instanceof String s) { // ok
            System.out.println(s);
        }
        else if(o instanceof Integer i) {  // violation
            System.out.println("not using 'i'!");
        }
        else {
            System.out.println("something else!");
        }
    }
}

```

Read more about pattern variables at https://openjdk.java.net/jeps/394
@nmancus1 @pbludov @strkkk  While adding support for pattern variables ( adding support for if-then-else right now, will discuss later adding support for ternary, etc.)  I would need to negate the expression apply de morgans law to an expression to check whether it will exist in else statement.
Examples will explain better-
```
String p,s,n; // instance variables

// inside some method
Object o = 45;
if (!(o instanceof String p && p.equals("sd")) || !p.equals("wq")) {
    p = ""; // local var p not in scope here
}
else if (!(o instanceof Integer s && o instanceof String n)) {
    p = "a"; // local var p in scope here
    s = ""; // local var s not in scope here
    n = ""; // local var n not in scope here
}
else {
    p = "a"; // local var p in scope here
    s = 41; // local var s in scope here
    n = ""; // local var n in scope here
}
```
So to evaluate the scopes of variables I would have to negate the expression apply de morgans law on an expression and obtain that expression in form of an AST.
For something like-
```
!(o instanceof String p && p.equals("sd")) || !p.equals("wq")

--EXPR -> EXPR [82:63]
   `--LOR -> || [82:63]
       |--LNOT -> ! [82:20]
       |   |--LPAREN -> ( [82:21]
       |   |--LAND -> && [82:44]
       |   |   |--LITERAL_INSTANCEOF -> instanceof [82:24]
       |   |   |   |--IDENT -> o [82:22]
       |   |   |   `--PATTERN_VARIABLE_DEF -> PATTERN_VARIABLE_DEF [82:35]
       |   |   |       |--MODIFIERS -> MODIFIERS [82:35]
       |   |   |       |--TYPE -> TYPE [82:35]
       |   |   |       |   `--IDENT -> String [82:35]
       |   |   |       `--IDENT -> p [82:42]
       |   |   `--METHOD_CALL -> ( [82:55]
       |   |       |--DOT -> . [82:48]
       |   |       |   |--IDENT -> p [82:47]
       |   |       |   `--IDENT -> equals [82:49]
       |   |       |--ELIST -> ELIST [82:56]
       |   |       |   `--EXPR -> EXPR [82:56]
       |   |       |       `--STRING_LITERAL -> "sd" [82:56]
       |   |       `--RPAREN -> ) [82:60]
       |   `--RPAREN -> ) [82:61]
       `--LNOT -> ! [82:66]
           `--METHOD_CALL -> ( [82:75]
               |--DOT -> . [82:68]
               |   |--IDENT -> p [82:67]
               |   `--IDENT -> equals [82:69]
               |--ELIST -> ELIST [82:76]
               |   `--EXPR -> EXPR [82:76]
               |       `--STRING_LITERAL -> "wq" [82:76]
               `--RPAREN -> ) [82:80]
```
I would like something like-
```
(o instanceof String p && p.equals("sd")) && p.equals("wq")

--EXPR -> EXPR [82:62]
   `--LAND -> && [82:62]
       |--LPAREN -> ( [82:20]
       |--LAND -> && [82:43]
       |   |--LITERAL_INSTANCEOF -> instanceof [82:23]
       |   |   |--IDENT -> o [82:21]
       |   |   `--PATTERN_VARIABLE_DEF -> PATTERN_VARIABLE_DEF [82:34]
       |   |       |--MODIFIERS -> MODIFIERS [82:34]
       |   |       |--TYPE -> TYPE [82:34]
       |   |       |   `--IDENT -> String [82:34]
       |   |       `--IDENT -> p [82:41]
       |   `--METHOD_CALL -> ( [82:54]
       |       |--DOT -> . [82:47]
       |       |   |--IDENT -> p [82:46]
       |       |   `--IDENT -> equals [82:48]
       |       |--ELIST -> ELIST [82:55]
       |       |   `--EXPR -> EXPR [82:55]
       |       |       `--STRING_LITERAL -> "sd" [82:55]
       |       `--RPAREN -> ) [82:59]
       |--RPAREN -> ) [82:60]
       `--METHOD_CALL -> ( [82:73]
           |--DOT -> . [82:66]
           |   |--IDENT -> p [82:65]
           |   `--IDENT -> equals [82:67]
           |--ELIST -> ELIST [82:74]
           |   `--EXPR -> EXPR [82:74]
           |       `--STRING_LITERAL -> "wq" [82:74]
           `--RPAREN -> ) [82:78]
```
The only option I can see is to take the `EXPR` node and create new nodes according to the logic and add the respective descendants.
Any ideas will be appreciated.
 
@Vyom-Yadav thanks for your investigation and explanation of nuances of implementing pattern variable support. I think in light of complicated implementation, and recent extension of pattern variables proposed by https://openjdk.java.net/jeps/405 and https://openjdk.java.net/jeps/406, perhaps pattern variables are a special enough case to require their own check.

We should clearly mention this limitation in the documentation (that it does not check pattern variables) for `UnusedLocalVariableCheck`.

@pbludov @strkkk what do you think?

Edit: Just leaving my thoughts about proposed pattern variable check:

> The only option I can see is to take the EXPR node and create new nodes according to the logic and add the respective descendants.
Any ideas will be appreciated.

This is a good idea; I would probably take the expression AST, and write some method to "reduce" the tree (having helper methods as "operators" for the reduction process), then evaluate the resulting reduced expression.


> While adding support for pattern variables ( adding support for if-then-else right now, will discuss later adding support for ternary, etc.) I would need to negate the expression apply de morgans law to an expression to check whether it will exist in else statement.

You basically need to track the frames like `RequireThisCheck` to understand if `p` is the first local variable or if it is the pattern variable instead. Also the pattern variable is basically another local variable, so I can see someone wanting it to check those too sometime. You may want to check how `RequireThisCheck` does this.

I think it is better to build the check in small steps and create new issues for the missing functionality. I can see people wanting pattern support.

This also makes me ask if `RequireThisCheck` has the necessary support for for patterns.
> You basically need to track the frames like RequireThisCheck to understand if p is the first local variable or if it is the pattern variable instead.

Support for pattern variables is more complicated than this; the expressions in the conditionals must be evaluated to determine the scope of the variable.
> the expressions in the conditionals must be evaluated to determine the scope of the variable.

Which is what `RequireThisCheck` attempts to do with it's "frames". It sees `{}` as a separate frame and see the distinction between the different scopes. But like you said, the scope/frame is not connected to the `{}` in that instance.
>     p = "a"; // local var p in scope here

Shouldn't this be the instance variable here? The scope of local p expired after the `if`. The same with some of the others. Shouldn't everything in the final `else` be the instance variables?

Edit: Like @nmancus1 said, I didn't realize how far the scope was for these variables. I thought they only applied to their blocks and not further.
`if (!(o instanceof String p && p.equals("sd"))....` means that pattern variable `p` is in scope of the else block, since the condition is basically `!(o instanceof String p && p.equals("sd"`). This is why we must evaluate the conditionals to verify the scope of a pattern variable, and also use frames like `RequireThisCheck` as well.

Edit:
Working example:

```java
public class Test {
    static String a = "instance var a!";

    static void method1(Object c) {
        if (c instanceof String a) {
            System.out.println("in if block: " + a);
        }
        else {
            System.out.println("in else block: " + a);
        }
    }

    static void method2(Object c) {
        if (!(c instanceof String a)) {
            System.out.println("in if block: " + a);
        }
        else {
            System.out.println("in else block: " + a);
        }
    }

    public static void main(String[] args) {
        method1("pattern variable a!");
        method2("pattern variable a!");
    }
}


```

Yields:
```bash
in if block: pattern variable a!
in else block: pattern variable a!

```



Patch:
diff --git a/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionUnusedLocalVariableTest.java b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionUnusedLocalVariableTest.java
new file mode 100644
--- /dev/null
+++ b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionUnusedLocalVariableTest.java
@@ -0,0 +1,108 @@
+////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code for adherence to a set of rules.
+// Copyright (C) 2001-2022 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.UnusedLocalVariableCheck;
+
+public class XpathRegressionUnusedLocalVariableTest extends AbstractXpathTestSupport {
+    private final String checkName = UnusedLocalVariableCheck.class.getSimpleName();
+
+    @Override
+    protected String getCheckName() {
+        return checkName;
+    }
+
+    @Test
+    public void testOne() throws Exception {
+        final File fileToProcess = new File(getPath(
+                "SuppressionXpathRegressionUnusedLocalVariableOne.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(UnusedLocalVariableCheck.class);
+
+        final String[] expectedViolation = {
+            "6:9: " + getCheckMessage(UnusedLocalVariableCheck.class,
+                    UnusedLocalVariableCheck.MSG_UNUSED_LOCAL_VARIABLE, "a"),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableOne']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF[./IDENT[@text='a']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableOne']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF["
+                        + "./IDENT[@text='a']]/MODIFIERS",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableOne']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF["
+                        + "./IDENT[@text='a']]/TYPE",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableOne']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF["
+                        + "./IDENT[@text='a']]/TYPE/LITERAL_INT"
+        );
+
+        runVerifications(moduleConfig, fileToProcess, expectedViolation,
+                expectedXpathQueries);
+    }
+
+    @Test
+    public void testTwo() throws Exception {
+        final File fileToProcess = new File(getPath(
+                "SuppressionXpathRegressionUnusedLocalVariableTwo.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(UnusedLocalVariableCheck.class);
+
+        final String[] expectedViolation = {
+            "10:9: " + getCheckMessage(UnusedLocalVariableCheck.class,
+                    UnusedLocalVariableCheck.MSG_UNUSED_LOCAL_VARIABLE, "b"),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableTwo']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF[./IDENT[@text='b']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableTwo']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF["
+                        + "./IDENT[@text='b']]/MODIFIERS",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT["
+                        + "@text='SuppressionXpathRegressionUnusedLocalVariableTwo']]/OBJBLOCK/"
+                        + "METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF["
+                        + "./IDENT[@text='b']]/TYPE", "/COMPILATION_UNIT/CLASS_DEF["
+                        + "./IDENT[@text='SuppressionXpathRegressionUnusedLocalVariableTwo']]/"
+                        + "OBJBLOCK/METHOD_DEF[./IDENT[@text='foo']]/SLIST/VARIABLE_DEF["
+                        + "./IDENT[@text='b']]/TYPE/LITERAL_INT"
+        );
+
+        runVerifications(moduleConfig, fileToProcess, expectedViolation,
+                expectedXpathQueries);
+
+    }
+}
diff --git a/src/it/resources/org/checkstyle/suppressionxpathfilter/unusedlocalvariable/SuppressionXpathRegressionUnusedLocalVariableOne.java b/src/it/resources/org/checkstyle/suppressionxpathfilter/unusedlocalvariable/SuppressionXpathRegressionUnusedLocalVariableOne.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources/org/checkstyle/suppressionxpathfilter/unusedlocalvariable/SuppressionXpathRegressionUnusedLocalVariableOne.java
@@ -0,0 +1,8 @@
+package org.checkstyle.suppressionxpathfilter.unusedlocalvariable;
+
+public class SuppressionXpathRegressionUnusedLocalVariableOne {
+
+    public void foo() {
+        int a = 20; // warn
+    }
+}
diff --git a/src/it/resources/org/checkstyle/suppressionxpathfilter/unusedlocalvariable/SuppressionXpathRegressionUnusedLocalVariableTwo.java b/src/it/resources/org/checkstyle/suppressionxpathfilter/unusedlocalvariable/SuppressionXpathRegressionUnusedLocalVariableTwo.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources/org/checkstyle/suppressionxpathfilter/unusedlocalvariable/SuppressionXpathRegressionUnusedLocalVariableTwo.java
@@ -0,0 +1,17 @@
+package org.checkstyle.suppressionxpathfilter.unusedlocalvariable;
+
+import java.util.function.Predicate;
+
+public class SuppressionXpathRegressionUnusedLocalVariableTwo {
+
+    int b = 21;
+
+    void foo() {
+        int b = 12; // warn
+        a(this.b);
+    }
+
+    static void a(int a) {
+    }
+
+}
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
@@ -571,6 +571,8 @@ private static void fillChecksFromCodingPackage() {
                 BASE_PACKAGE + ".checks.coding.NoArrayTrailingCommaCheck");
         NAME_TO_FULL_MODULE_NAME.put("MatchXpathCheck",
                 BASE_PACKAGE + ".checks.coding.MatchXpathCheck");
+        NAME_TO_FULL_MODULE_NAME.put("UnusedLocalVariableCheck",
+                BASE_PACKAGE + ".checks.coding.UnusedLocalVariableCheck");
     }
 
     /**
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/api/FullIdent.java b/src/main/java/com/puppycrawl/tools/checkstyle/api/FullIdent.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/api/FullIdent.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/api/FullIdent.java
@@ -85,7 +85,13 @@ private static void extractFullIdent(FullIdent full, DetailAST ast) {
                         || nextSibling.getType() == TokenTypes.ANNOTATIONS)
                     && isArrayTypeDeclaration(nextSibling);
 
-            if (ast.getType() == TokenTypes.DOT) {
+            final int typeOfAst = ast.getType();
+            if (typeOfAst == TokenTypes.LITERAL_NEW
+                    && ast.hasChildren()) {
+                final DetailAST firstChild = ast.getFirstChild();
+                extractFullIdent(full, firstChild);
+            }
+            else if (typeOfAst == TokenTypes.DOT) {
                 final DetailAST firstChild = ast.getFirstChild();
                 extractFullIdent(full, firstChild);
                 full.append(".");
@@ -96,7 +102,7 @@ else if (isArrayTypeDeclarationStart) {
                 full.append(ast);
                 appendBrackets(full, ast);
             }
-            else if (ast.getType() != TokenTypes.ANNOTATIONS) {
+            else if (typeOfAst != TokenTypes.ANNOTATIONS) {
                 full.append(ast);
             }
         }
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedLocalVariableCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedLocalVariableCheck.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedLocalVariableCheck.java
@@ -0,0 +1,1135 @@
+////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code for adherence to a set of rules.
+// Copyright (C) 2001-2022 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.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.stream.Collectors;
+
+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.FullIdent;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
+import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
+import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
+
+/**
+ * <p>
+ * Checks that a local variable is declared and/or assigned, but not used.
+ * Doesn't support
+ * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html#jls-14.30">
+ * pattern variables yet</a>.
+ * Doesn't check
+ * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.12.3">
+ * array components</a> as array
+ * components are classified as different kind of variables by
+ * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/index.html">JLS</a>.
+ * </p>
+ * <p>
+ * To configure the check:
+ * </p>
+ * <pre>
+ * &lt;module name=&quot;UnusedLocalVariable&quot;/&gt;
+ * </pre>
+ * <p>
+ * Example:
+ * </p>
+ * <pre>
+ * class Test {
+ *
+ *     int a;
+ *
+ *     {
+ *         int k = 12; // violation, assigned and updated but never used
+ *         k++;
+ *     }
+ *
+ *     Test(int a) {   // ok as 'a' is a constructor parameter not a local variable
+ *         this.a = 12;
+ *     }
+ *
+ *     void method(int b) {
+ *         int a = 10;             // violation
+ *         int[] arr = {1, 2, 3};  // violation
+ *         int[] anotherArr = {1}; // ok
+ *         anotherArr[0] = 4;
+ *     }
+ *
+ *     String convertValue(String newValue) {
+ *         String s = newValue.toLowerCase(); // violation
+ *         return newValue.toLowerCase();
+ *     }
+ *
+ *     void read() throws IOException {
+ *         BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ *         String s; // violation
+ *         while ((s = reader.readLine()) != null) {
+ *         }
+ *         try (BufferedReader reader1 // ok as 'reader1' is a resource and resources are closed
+ *                                     // at the end of the statement
+ *             = new BufferedReader(new FileReader("abc.txt"))) {
+ *         }
+ *         try {
+ *         } catch (Exception e) {     // ok as e is an exception parameter
+ *         }
+ *     }
+ *
+ *     void loops() {
+ *         int j = 12;
+ *         for (int i = 0; j &lt; 11; i++) { // violation, unused local variable 'i'.
+ *         }
+ *         for (int p = 0; j &lt; 11; p++)   // ok
+ *             p /= 2;
+ *     }
+ *
+ *     void lambdas() {
+ *         Predicate&lt;String&gt; obj = (String str) -&gt; { // ok as 'str' is a lambda parameter
+ *             return true;
+ *         };
+ *         obj.test("test");
+ *     }
+ * }
+ * </pre>
+ * <p>
+ * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
+ * </p>
+ * <p>
+ * Violation Message Keys:
+ * </p>
+ * <ul>
+ * <li>
+ * {@code unused.local.var}
+ * </li>
+ * </ul>
+ *
+ * @since 9.3
+ */
+@FileStatefulCheck
+public class UnusedLocalVariableCheck extends AbstractCheck {
+
+    /**
+     * A key is pointing to the warning message text in "messages.properties"
+     * file.
+     */
+    public static final String MSG_UNUSED_LOCAL_VARIABLE = "unused.local.var";
+
+    /**
+     * An array of increment and decrement tokens.
+     */
+    private static final int[] INCREMENT_AND_DECREMENT_TOKENS = {
+        TokenTypes.POST_INC,
+        TokenTypes.POST_DEC,
+        TokenTypes.INC,
+        TokenTypes.DEC,
+    };
+
+    /**
+     * An array of scope tokens.
+     */
+    private static final int[] SCOPES = {
+        TokenTypes.SLIST,
+        TokenTypes.LITERAL_FOR,
+        TokenTypes.OBJBLOCK,
+    };
+
+    /**
+     * An array of unacceptable children of ast of type {@link TokenTypes#DOT}.
+     */
+    private static final int[] UNACCEPTABLE_CHILD_OF_DOT = {
+        TokenTypes.DOT,
+        TokenTypes.METHOD_CALL,
+        TokenTypes.LITERAL_NEW,
+        TokenTypes.LITERAL_SUPER,
+        TokenTypes.LITERAL_CLASS,
+        TokenTypes.LITERAL_THIS,
+    };
+
+    /**
+     * An array of unacceptable parent of ast of type {@link TokenTypes#IDENT}.
+     */
+    private static final int[] UNACCEPTABLE_PARENT_OF_IDENT = {
+        TokenTypes.VARIABLE_DEF,
+        TokenTypes.DOT,
+        TokenTypes.LITERAL_NEW,
+        TokenTypes.PATTERN_VARIABLE_DEF,
+        TokenTypes.METHOD_CALL,
+        TokenTypes.TYPE,
+    };
+
+    /**
+     * An array of blocks in which local anon inner classes can exist.
+     */
+    private static final int[] CONTAINERS_FOR_ANON_INNERS = {
+        TokenTypes.METHOD_DEF,
+        TokenTypes.CTOR_DEF,
+        TokenTypes.STATIC_INIT,
+        TokenTypes.INSTANCE_INIT,
+        TokenTypes.COMPACT_CTOR_DEF,
+    };
+
+    /** Package separator. */
+    private static final String PACKAGE_SEPARATOR = ".";
+
+    /**
+     * Keeps tracks of the variables declared in file.
+     */
+    private final Deque<VariableDesc> variables;
+
+    /**
+     * Keeps track of all the type declarations present in the file.
+     * Pops the type out of the stack while leaving the type
+     * in visitor pattern.
+     */
+    private final Deque<TypeDeclDesc> typeDeclarations;
+
+    /**
+     * Maps type declaration ast to their respective TypeDeclDesc objects.
+     */
+    private final Map<DetailAST, TypeDeclDesc> typeDeclAstToTypeDeclDesc;
+
+    /**
+     * Maps local anonymous inner class to the TypeDeclDesc object
+     * containing it.
+     */
+    private final Map<DetailAST, TypeDeclDesc> anonInnerAstToTypeDeclDesc;
+
+    /**
+     * Queue of tokens of type {@link UnusedLocalVariableCheck#CONTAINERS_FOR_ANON_INNERS}
+     * and {@link TokenTypes#LAMBDA} in some cases.
+     */
+    private final Queue<DetailAST> anonInnerClassHolders;
+
+    /**
+     * Name of the package.
+     */
+    private String packageName;
+
+    /**
+     * Depth at which a type declaration is nested, 0 for top level type declarations.
+     */
+    private int depth;
+
+    /**
+     * Block containing local anon inner class, keeps updating throughout the check.
+     * Acts as a tracker and checks whether the current node being visited is present
+     * inside the block containing a local anonymous inner class.
+     */
+    private DetailAST blockContainingLocalAnonInnerClass;
+
+    /**
+     * Creates a new {@code UnusedLocalVariableCheck} instance.
+     */
+    public UnusedLocalVariableCheck() {
+        variables = new ArrayDeque<>();
+        typeDeclarations = new ArrayDeque<>();
+        typeDeclAstToTypeDeclDesc = new HashMap<>();
+        anonInnerAstToTypeDeclDesc = new HashMap<>();
+        anonInnerClassHolders = new ArrayDeque<>();
+        blockContainingLocalAnonInnerClass = null;
+        packageName = "";
+        depth = 0;
+    }
+
+    @Override
+    public int[] getDefaultTokens() {
+        return new int[] {
+            TokenTypes.DOT,
+            TokenTypes.VARIABLE_DEF,
+            TokenTypes.IDENT,
+            TokenTypes.SLIST,
+            TokenTypes.LITERAL_FOR,
+            TokenTypes.OBJBLOCK,
+            TokenTypes.CLASS_DEF,
+            TokenTypes.INTERFACE_DEF,
+            TokenTypes.ANNOTATION_DEF,
+            TokenTypes.PACKAGE_DEF,
+            TokenTypes.LITERAL_NEW,
+            TokenTypes.METHOD_DEF,
+            TokenTypes.CTOR_DEF,
+            TokenTypes.STATIC_INIT,
+            TokenTypes.INSTANCE_INIT,
+            TokenTypes.COMPILATION_UNIT,
+            TokenTypes.LAMBDA,
+            TokenTypes.ENUM_DEF,
+            TokenTypes.RECORD_DEF,
+            TokenTypes.COMPACT_CTOR_DEF,
+        };
+    }
+
+    @Override
+    public int[] getAcceptableTokens() {
+        return getDefaultTokens();
+    }
+
+    @Override
+    public int[] getRequiredTokens() {
+        return getDefaultTokens();
+    }
+
+    @Override
+    public void beginTree(DetailAST root) {
+        // No need to set blockContainingLocalAnonInnerClass to null, if
+        // its value gets changed during the check then it is changed back to null
+        // inside the check only.
+        variables.clear();
+        typeDeclarations.clear();
+        typeDeclAstToTypeDeclDesc.clear();
+        anonInnerAstToTypeDeclDesc.clear();
+        anonInnerClassHolders.clear();
+        packageName = "";
+        depth = 0;
+    }
+
+    @Override
+    public void visitToken(DetailAST ast) {
+        final int type = ast.getType();
+        if (type == TokenTypes.DOT) {
+            visitDotToken(ast, variables);
+        }
+        else if (type == TokenTypes.VARIABLE_DEF) {
+            visitVariableDefToken(ast);
+        }
+        else if (type == TokenTypes.IDENT) {
+            visitIdentToken(ast, variables);
+        }
+        else if (TokenUtil.isTypeDeclaration(type)) {
+            visitTypeDeclarationToken(ast);
+        }
+        else if (type == TokenTypes.PACKAGE_DEF) {
+            packageName = extractQualifiedName(ast.getFirstChild().getNextSibling());
+        }
+        else if (type == TokenTypes.LITERAL_NEW
+                && isInsideLocalAnonInnerClass(ast)) {
+            visitLocalAnonInnerClass(ast);
+        }
+    }
+
+    @Override
+    public void leaveToken(DetailAST ast) {
+        if (TokenUtil.isOfType(ast, SCOPES)) {
+            logViolations(ast, variables);
+        }
+        else if (ast.getType() == TokenTypes.COMPILATION_UNIT) {
+            leaveCompilationUnit();
+        }
+        else if (ast.equals(blockContainingLocalAnonInnerClass)) {
+            blockContainingLocalAnonInnerClass = null;
+        }
+        else if (isNonLocalTypeDeclaration(ast)) {
+            depth--;
+            typeDeclarations.pop();
+        }
+    }
+
+    /**
+     * Visit ast of type {@link TokenTypes#DOT}.
+     *
+     * @param dotAst dotAst
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private void visitDotToken(DetailAST dotAst, Deque<VariableDesc> variablesStack) {
+        if (blockContainingLocalAnonInnerClass == null
+                && dotAst.getParent().getType() != TokenTypes.LITERAL_NEW
+                && shouldCheckIdentTokenNestedUnderDot(dotAst)) {
+            checkIdentifierAst(dotAst.findFirstToken(TokenTypes.IDENT), variablesStack);
+        }
+    }
+
+    /**
+     * Visit ast of type {@link TokenTypes#VARIABLE_DEF}.
+     *
+     * @param varDefAst varDefAst
+     */
+    private void visitVariableDefToken(DetailAST varDefAst) {
+        if (blockContainingLocalAnonInnerClass == null) {
+            addLocalVariables(varDefAst, variables);
+            addInstanceOrClassVar(varDefAst);
+        }
+    }
+
+    /**
+     * Visit ast of type {@link TokenTypes#IDENT}.
+     *
+     * @param identAst identAst
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private void visitIdentToken(DetailAST identAst, Deque<VariableDesc> variablesStack) {
+        if (blockContainingLocalAnonInnerClass == null) {
+            final DetailAST parentAst = identAst.getParent();
+            if (!TokenUtil.isOfType(parentAst, UNACCEPTABLE_PARENT_OF_IDENT)
+                    && shouldCheckIdentWithMethodRefParent(identAst)) {
+                checkIdentifierAst(identAst, variablesStack);
+            }
+        }
+    }
+
+    /**
+     * Visit the type declaration token.
+     *
+     * @param typeDeclAst type declaration ast
+     */
+    private void visitTypeDeclarationToken(DetailAST typeDeclAst) {
+        if (isNonLocalTypeDeclaration(typeDeclAst)) {
+            final String qualifiedName = getQualifiedTypeDeclarationName(typeDeclAst);
+            final TypeDeclDesc currTypeDecl = new TypeDeclDesc(qualifiedName, depth, typeDeclAst);
+            depth++;
+            typeDeclarations.push(currTypeDecl);
+            typeDeclAstToTypeDeclDesc.put(typeDeclAst, currTypeDecl);
+        }
+    }
+
+    /**
+     * Visit the local anon inner class.
+     *
+     * @param literalNewAst literalNewAst
+     */
+    private void visitLocalAnonInnerClass(DetailAST literalNewAst) {
+        anonInnerAstToTypeDeclDesc.put(literalNewAst, typeDeclarations.peek());
+        if (blockContainingLocalAnonInnerClass == null) {
+            blockContainingLocalAnonInnerClass = getBlockContainingLocalAnonInnerClass(
+                    literalNewAst);
+            anonInnerClassHolders.add(blockContainingLocalAnonInnerClass);
+        }
+    }
+
+    /**
+     * Get name of package and super class of anon inner class by concatenating
+     * the identifier values under {@link TokenTypes#DOT}.
+     * Duplicated, until <a>https://github.com/checkstyle/checkstyle/issues/11201</a>
+     *
+     * @param ast ast to extract superclass or package name from
+     * @return qualified name
+     */
+    private static String extractQualifiedName(DetailAST ast) {
+        return FullIdent.createFullIdent(ast).getText();
+    }
+
+    /**
+     * Whether ast node of type {@link TokenTypes#LITERAL_NEW} is a part of a local
+     * anonymous inner class.
+     *
+     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
+     * @return true if variableDefAst is an instance variable in local anonymous inner class
+     */
+    private static boolean isInsideLocalAnonInnerClass(DetailAST literalNewAst) {
+        boolean result = false;
+        final DetailAST lastChild = literalNewAst.getLastChild();
+        if (lastChild != null && lastChild.getType() == TokenTypes.OBJBLOCK) {
+            DetailAST parentAst = literalNewAst.getParent();
+            while (parentAst.getType() != TokenTypes.SLIST) {
+                if (parentAst.getType() == TokenTypes.ENUM_CONSTANT_DEF
+                        || parentAst.getType() == TokenTypes.OBJBLOCK
+                        && TokenUtil.isTypeDeclaration(parentAst.getParent().getType())) {
+                    break;
+                }
+                parentAst = parentAst.getParent();
+            }
+            result = parentAst.getType() == TokenTypes.SLIST;
+        }
+        return result;
+    }
+
+    /**
+     * Traverse {@code variablesStack} stack and log the violations.
+     *
+     * @param scopeAst ast node of type {@link UnusedLocalVariableCheck#SCOPES}
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private void logViolations(DetailAST scopeAst, Deque<VariableDesc> variablesStack) {
+        while (!variablesStack.isEmpty() && variablesStack.peek().getScope() == scopeAst) {
+            final VariableDesc variableDesc = variablesStack.pop();
+            if (blockContainingLocalAnonInnerClass == null
+                    && !variableDesc.isUsed()
+                    && !variableDesc.isInstVarOrClassVar()) {
+                final DetailAST typeAst = variableDesc.getTypeAst();
+                log(typeAst, MSG_UNUSED_LOCAL_VARIABLE, variableDesc.getName());
+            }
+        }
+    }
+
+    /**
+     * We process all the blocks containing local anonymous inner classes
+     * separately after processing all the other nodes. This is being done
+     * due to the fact the instance variables of local anon inner classes can
+     * cast a shadow on local variables.
+     */
+    private void leaveCompilationUnit() {
+        while (!anonInnerClassHolders.isEmpty()) {
+            final DetailAST holder = anonInnerClassHolders.remove();
+            iterateOverBlockContainingLocalAnonInnerClass(holder, new ArrayDeque<>());
+        }
+    }
+
+    /**
+     * Whether a type declaration is non-local. Annotated interfaces are always non-local.
+     *
+     * @param typeDeclAst type declaration ast
+     * @return true if type declaration is non-local
+     */
+    private static boolean isNonLocalTypeDeclaration(DetailAST typeDeclAst) {
+        return typeDeclAst.getParent().getType() != TokenTypes.SLIST
+                && TokenUtil.isTypeDeclaration(typeDeclAst.getType());
+    }
+
+    /**
+     * Get the block containing local anon inner class.
+     *
+     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
+     * @return the block containing local anon inner class
+     */
+    private static DetailAST getBlockContainingLocalAnonInnerClass(DetailAST literalNewAst) {
+        DetailAST parentAst = literalNewAst.getParent();
+        DetailAST result = null;
+        while (!TokenUtil.isOfType(parentAst, CONTAINERS_FOR_ANON_INNERS)) {
+            if (parentAst.getType() == TokenTypes.LAMBDA
+                    && parentAst.getParent()
+                    .getParent().getParent().getType() == TokenTypes.OBJBLOCK) {
+                result = parentAst;
+                break;
+            }
+            parentAst = parentAst.getParent();
+            result = parentAst;
+        }
+        return result;
+    }
+
+    /**
+     * Add local variables to the {@code variablesStack} stack.
+     * Also adds the instance variables defined in a local anonymous inner class.
+     *
+     * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private static void addLocalVariables(DetailAST varDefAst, Deque<VariableDesc> variablesStack) {
+        final DetailAST parentAst = varDefAst.getParent();
+        final DetailAST grandParent = parentAst.getParent();
+        final boolean isInstanceVarInLocalAnonymousInnerClass =
+                grandParent.getType() == TokenTypes.LITERAL_NEW
+                        && isInsideLocalAnonInnerClass(grandParent);
+        if (isInstanceVarInLocalAnonymousInnerClass
+                || parentAst.getType() != TokenTypes.OBJBLOCK) {
+            final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
+            final VariableDesc desc = new VariableDesc(ident.getText(),
+                    varDefAst.findFirstToken(TokenTypes.TYPE), findScopeOfVariable(varDefAst));
+            if (isInstanceVarInLocalAnonymousInnerClass) {
+                desc.registerAsInstOrClassVar();
+            }
+            variablesStack.push(desc);
+        }
+    }
+
+    /**
+     * Add instance variables and class variables to the
+     * {@link TypeDeclDesc#instanceAndClassVarStack}.
+     *
+     * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
+     */
+    private void addInstanceOrClassVar(DetailAST varDefAst) {
+        final DetailAST parentAst = varDefAst.getParent();
+        if (parentAst.getType() == TokenTypes.OBJBLOCK
+                && isNonLocalTypeDeclaration(parentAst.getParent())
+                && !isPrivateInstanceVariable(varDefAst)) {
+            final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
+            final VariableDesc desc = new VariableDesc(ident.getText(),
+                    varDefAst.findFirstToken(TokenTypes.TYPE), findScopeOfVariable(varDefAst));
+            typeDeclAstToTypeDeclDesc.get(parentAst.getParent()).addInstOrClassVar(desc);
+        }
+    }
+
+    /**
+     * Whether instance variable or class variable have private access modifier.
+     *
+     * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
+     * @return true if instance variable or class variable have private access modifier
+     */
+    private static boolean isPrivateInstanceVariable(DetailAST varDefAst) {
+        final AccessModifierOption varAccessModifier =
+                CheckUtil.getAccessModifierFromModifiersToken(varDefAst);
+        return varAccessModifier == AccessModifierOption.PRIVATE;
+    }
+
+    /**
+     * Get the {@link TypeDeclDesc} of the super class of anonymous inner class.
+     *
+     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
+     * @return {@link TypeDeclDesc} of the super class of anonymous inner class
+     */
+    private TypeDeclDesc getSuperClassOfAnonInnerClass(DetailAST literalNewAst) {
+        TypeDeclDesc obtainedClass = null;
+        final String shortNameOfClass = getShortNameOfAnonInnerClass(literalNewAst);
+
+        final List<TypeDeclDesc> typeDeclWithSameName = typeDeclWithSameName(shortNameOfClass);
+        if (!typeDeclWithSameName.isEmpty()) {
+            obtainedClass = getTheNearestClass(
+                    anonInnerAstToTypeDeclDesc.get(literalNewAst).getQualifiedName(),
+                    typeDeclWithSameName);
+        }
+        return obtainedClass;
+    }
+
+    /**
+     * Get the short name of super class of anonymous inner class.
+     * Example-
+     * <pre>
+     * TestClass.NestedClass obj = new Test().new NestedClass() {};
+     * // Short name will be Test.NestedClass
+     * </pre>
+     *
+     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
+     * @return short name of base class of anonymous inner class
+     */
+    public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
+        DetailAST parentAst = literalNewAst.getParent();
+        while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
+            parentAst = parentAst.getParent();
+        }
+        final DetailAST firstChild = parentAst.getFirstChild();
+        return extractQualifiedName(firstChild);
+    }
+
+    /**
+     * Add non-private instance and class variables of the super class of the anonymous class
+     * to the variables stack.
+     *
+     * @param obtainedClass super class of the anon inner class
+     * @param variablesStack stack of all the relevant variables in the scope
+     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
+     */
+    private void modifyVariablesStack(TypeDeclDesc obtainedClass,
+            Deque<VariableDesc> variablesStack,
+            DetailAST literalNewAst) {
+        if (obtainedClass != null) {
+            final Deque<VariableDesc> instAndClassVarDeque = typeDeclAstToTypeDeclDesc
+                    .get(obtainedClass.getTypeDeclAst())
+                    .getUpdatedCopyOfVarStack(literalNewAst);
+            instAndClassVarDeque.forEach(variablesStack::push);
+        }
+    }
+
+    /**
+     * Checks if there is a type declaration with same name as the super class.
+     * Duplicated, until <a>https://github.com/checkstyle/checkstyle/issues/11201</a>
+     *
+     * @param superClassName name of the super class
+     * @return true if there is another type declaration with same name.
+     * @noinspection CallToStringConcatCanBeReplacedByOperator
+     */
+    private List<TypeDeclDesc> typeDeclWithSameName(String superClassName) {
+        return typeDeclAstToTypeDeclDesc.values().stream()
+                .filter(typeDeclDesc -> {
+                    return typeDeclDesc.getQualifiedName()
+                            .endsWith(PACKAGE_SEPARATOR.concat(superClassName));
+                })
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * For all type declarations with the same name as the superclass, gets the nearest type
+     * declaration.
+     * Duplicated, until <a>https://github.com/checkstyle/checkstyle/issues/11201</a>
+     *
+     * @param outerTypeDeclName outer type declaration of anonymous inner class
+     * @param typeDeclWithSameName typeDeclarations which have the same name as the super class
+     * @return the nearest class
+     */
+    private static TypeDeclDesc getTheNearestClass(String outerTypeDeclName,
+            List<TypeDeclDesc> typeDeclWithSameName) {
+        return Collections.min(typeDeclWithSameName, (first, second) -> {
+            int diff = Integer.compare(
+                    typeDeclNameMatchingCount(outerTypeDeclName, second.getQualifiedName()),
+                    typeDeclNameMatchingCount(outerTypeDeclName, first.getQualifiedName()));
+            if (diff == 0) {
+                diff = Integer.compare(first.getDepth(), second.getDepth());
+            }
+            return diff;
+        });
+    }
+
+    /**
+     * Calculates and returns the type declaration name matching count.
+     *
+     * <p>
+     * Suppose our pattern class is {@code foo.a.b} and class to be matched is
+     * {@code foo.a.ball} then type declaration name matching count would be calculated by
+     * comparing every character, and updating main counter when we hit "." to prevent matching
+     * "a.b" with "a.ball". In this case type declaration name matching count
+     * would be equal to 6 and not 7 (b of ball is not counted).
+     * </p>
+     * Duplicated, until <a>https://github.com/checkstyle/checkstyle/issues/11201</a>
+     *
+     * @param patternClass class against which the given class has to be matched
+     * @param classToBeMatched class to be matched
+     * @return class name matching count
+     */
+    private static int typeDeclNameMatchingCount(String patternClass, String classToBeMatched) {
+        final char packageSeparator = PACKAGE_SEPARATOR.charAt(0);
+        final int length = Math.min(classToBeMatched.length(), patternClass.length());
+        int result = 0;
+        for (int i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
+            if (patternClass.charAt(i) == packageSeparator) {
+                result = i;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Get qualified type declaration name from type ast.
+     * Duplicated, until <a>https://github.com/checkstyle/checkstyle/issues/11201</a>
+     *
+     * @param typeDeclAst type declaration ast
+     * @return qualified name of type declaration
+     */
+    private String getQualifiedTypeDeclarationName(DetailAST typeDeclAst) {
+        final String className = typeDeclAst.findFirstToken(TokenTypes.IDENT).getText();
+        String outerClassQualifiedName = null;
+        if (!typeDeclarations.isEmpty()) {
+            outerClassQualifiedName = typeDeclarations.peek().getQualifiedName();
+        }
+        return getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className);
+    }
+
+    /**
+     * Get the qualified name of type declaration by combining {@code packageName},
+     * {@code outerClassQualifiedName} and {@code className}.
+     * Duplicated, until <a>https://github.com/checkstyle/checkstyle/issues/11201</a>
+     *
+     * @param packageName packageName
+     * @param outerClassQualifiedName outerClassQualifiedName
+     * @param className className
+     * @return the qualified name of type declaration by combining {@code packageName},
+     *         {@code outerClassQualifiedName} and {@code className}
+     */
+    private static String getQualifiedTypeDeclarationName(String packageName,
+            String outerClassQualifiedName, String className) {
+        final String qualifiedClassName;
+
+        if (outerClassQualifiedName == null) {
+            if (packageName.isEmpty()) {
+                qualifiedClassName = className;
+            }
+            else {
+                qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
+            }
+        }
+        else {
+            qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
+        }
+        return qualifiedClassName;
+    }
+
+    /**
+     * Iterate over all the ast nodes present under {@code ast}.
+     *
+     * @param ast ast
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private void iterateOverBlockContainingLocalAnonInnerClass(
+            DetailAST ast, Deque<VariableDesc> variablesStack) {
+        DetailAST currNode = ast;
+        while (currNode != null) {
+            customVisitToken(currNode, variablesStack);
+            DetailAST toVisit = currNode.getFirstChild();
+            while (currNode != ast && toVisit == null) {
+                customLeaveToken(currNode, variablesStack);
+                toVisit = currNode.getNextSibling();
+                currNode = currNode.getParent();
+            }
+            currNode = toVisit;
+        }
+    }
+
+    /**
+     * Visit all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once
+     * again.
+     *
+     * @param ast ast
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private void customVisitToken(DetailAST ast, Deque<VariableDesc> variablesStack) {
+        final int type = ast.getType();
+        if (type == TokenTypes.DOT) {
+            visitDotToken(ast, variablesStack);
+        }
+        else if (type == TokenTypes.VARIABLE_DEF) {
+            addLocalVariables(ast, variablesStack);
+        }
+        else if (type == TokenTypes.IDENT) {
+            visitIdentToken(ast, variablesStack);
+        }
+        else if (type == TokenTypes.LITERAL_NEW
+                && isInsideLocalAnonInnerClass(ast)) {
+            final TypeDeclDesc obtainedClass = getSuperClassOfAnonInnerClass(ast);
+            modifyVariablesStack(obtainedClass, variablesStack, ast);
+        }
+    }
+
+    /**
+     * Visit all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once
+     * again.
+     *
+     * @param ast ast
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private void customLeaveToken(DetailAST ast, Deque<VariableDesc> variablesStack) {
+        if (TokenUtil.isOfType(ast, SCOPES)) {
+            logViolations(ast, variablesStack);
+        }
+    }
+
+    /**
+     * Whether an ident with parent node of type {@link TokenTypes#METHOD_REF}
+     * should be checked or not.
+     *
+     * @param identAst identAst
+     * @return true if an ident with parent node of type {@link TokenTypes#METHOD_REF}
+     *         should be checked or if the parent type is not {@link TokenTypes#METHOD_REF}
+     */
+    public static boolean shouldCheckIdentWithMethodRefParent(DetailAST identAst) {
+        final DetailAST parent = identAst.getParent();
+        boolean result = true;
+        if (parent.getType() == TokenTypes.METHOD_REF) {
+            result = parent.getFirstChild() == identAst
+                    && parent.getLastChild().getType() != TokenTypes.LITERAL_NEW;
+        }
+        return result;
+    }
+
+    /**
+     * Whether to check identifier token nested under dotAst.
+     *
+     * @param dotAst dotAst
+     * @return true if ident nested under dotAst should be checked
+     */
+    public static boolean shouldCheckIdentTokenNestedUnderDot(DetailAST dotAst) {
+
+        return !TokenUtil.findFirstTokenByPredicate(dotAst,
+                        childAst -> {
+                            return TokenUtil.isOfType(childAst,
+                                    UNACCEPTABLE_CHILD_OF_DOT);
+                        })
+                .isPresent();
+    }
+
+    /**
+     * Checks the identifier ast.
+     *
+     * @param identAst ast of type {@link TokenTypes#IDENT}
+     * @param variablesStack stack of all the relevant variables in the scope
+     */
+    private static void checkIdentifierAst(DetailAST identAst, Deque<VariableDesc> variablesStack) {
+        for (VariableDesc variableDesc : variablesStack) {
+            if (identAst.getText().equals(variableDesc.getName())
+                    && !isLeftHandSideValue(identAst)) {
+                if (!variableDesc.isInstVarOrClassVar()) {
+                    variableDesc.registerAsUsed();
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Find the scope of variable.
+     *
+     * @param variableDef ast of type {@link TokenTypes#VARIABLE_DEF}
+     * @return scope of variableDef
+     */
+    private static DetailAST findScopeOfVariable(DetailAST variableDef) {
+        final DetailAST result;
+        final DetailAST parentAst = variableDef.getParent();
+        if (TokenUtil.isOfType(parentAst, TokenTypes.SLIST, TokenTypes.OBJBLOCK)) {
+            result = parentAst;
+        }
+        else {
+            final DetailAST grandParent = parentAst.getParent();
+            final DetailAST scopeAst = grandParent.findFirstToken(TokenTypes.SLIST);
+            if (scopeAst == null) {
+                result = grandParent;
+            }
+            else {
+                result = scopeAst;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Checks whether the ast of type {@link TokenTypes#IDENT} is
+     * used as left-hand side value. An identifier is being used as a left-hand side
+     * value if it is used as the left operand of an assignment or as an
+     * operand of a stand-alone increment or decrement.
+     *
+     * @param identAst ast of type {@link TokenTypes#IDENT}
+     * @return true if identAst is used as a left-hand side value
+     */
+    private static boolean isLeftHandSideValue(DetailAST identAst) {
+        final DetailAST parent = identAst.getParent();
+        return isStandAloneIncrementOrDecrement(identAst)
+                || parent.getType() == TokenTypes.ASSIGN
+                && identAst != parent.getLastChild();
+    }
+
+    /**
+     * Checks whether the ast of type {@link TokenTypes#IDENT} is used as
+     * an operand of a stand-alone increment or decrement.
+     *
+     * @param identAst ast of type {@link TokenTypes#IDENT}
+     * @return true if identAst is used as an operand of stand-alone
+     *         increment or decrement
+     */
+    private static boolean isStandAloneIncrementOrDecrement(DetailAST identAst) {
+        final DetailAST parent = identAst.getParent();
+        final DetailAST grandParent = parent.getParent();
+        return TokenUtil.isOfType(parent, INCREMENT_AND_DECREMENT_TOKENS)
+                && TokenUtil.isOfType(grandParent, TokenTypes.EXPR)
+                && !isIncrementOrDecrementVariableUsed(grandParent);
+    }
+
+    /**
+     * A variable with increment or decrement operator is considered used if it
+     * is used as an argument or as an array index or for assigning value
+     * to a variable.
+     *
+     * @param exprAst ast of type {@link TokenTypes#EXPR}
+     * @return true if variable nested in exprAst is used
+     */
+    private static boolean isIncrementOrDecrementVariableUsed(DetailAST exprAst) {
+        return TokenUtil.isOfType(exprAst.getParent(),
+                TokenTypes.ELIST, TokenTypes.INDEX_OP, TokenTypes.ASSIGN)
+                && exprAst.getParent().getParent().getType() != TokenTypes.FOR_ITERATOR;
+    }
+
+    /**
+     * Maintains information about the variable.
+     */
+    private static final class VariableDesc {
+
+        /**
+         * The name of the variable.
+         */
+        private final String name;
+
+        /**
+         * Ast of type {@link TokenTypes#TYPE}.
+         */
+        private final DetailAST typeAst;
+
+        /**
+         * The scope of variable is determined by the ast of type
+         * {@link TokenTypes#SLIST} or {@link TokenTypes#LITERAL_FOR}
+         * or {@link TokenTypes#OBJBLOCK} which is enclosing the variable.
+         */
+        private final DetailAST scope;
+
+        /**
+         * Is an instance variable or a class variable.
+         */
+        private boolean instVarOrClassVar;
+
+        /**
+         * Is the variable used.
+         */
+        private boolean used;
+
+        /**
+         * Create a new VariableDesc instance.
+         *
+         * @param name name of the variable
+         * @param typeAst ast of type {@link TokenTypes#TYPE}
+         * @param scope ast of type {@link TokenTypes#SLIST} or
+         *              {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
+         *              which is enclosing the variable
+         */
+        /* package */ VariableDesc(String name, DetailAST typeAst, DetailAST scope) {
+            this.name = name;
+            this.typeAst = typeAst;
+            this.scope = scope;
+        }
+
+        /**
+         * Get the name of variable.
+         *
+         * @return name of variable
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Get the associated ast node of type {@link TokenTypes#TYPE}.
+         *
+         * @return the associated ast node of type {@link TokenTypes#TYPE}
+         */
+        public DetailAST getTypeAst() {
+            return typeAst;
+        }
+
+        /**
+         * Get ast of type {@link TokenTypes#SLIST}
+         * or {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
+         * which is enclosing the variable i.e. its scope.
+         *
+         * @return the scope associated with the variable
+         */
+        public DetailAST getScope() {
+            return scope;
+        }
+
+        /**
+         * Register the variable as used.
+         */
+        public void registerAsUsed() {
+            used = true;
+        }
+
+        /**
+         * Register the variable as an instance variable or
+         * class variable.
+         */
+        public void registerAsInstOrClassVar() {
+            instVarOrClassVar = true;
+        }
+
+        /**
+         * Is the variable used or not.
+         *
+         * @return true if variable is used
+         */
+        public boolean isUsed() {
+            return used;
+        }
+
+        /**
+         * Is an instance variable or a class variable.
+         *
+         * @return true if is an instance variable or a class variable
+         */
+        public boolean isInstVarOrClassVar() {
+            return instVarOrClassVar;
+        }
+    }
+
+    /**
+     * Maintains information about the type declaration.
+     * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF}
+     * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF}
+     * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration.
+     */
+    private static class TypeDeclDesc {
+
+        /**
+         * Complete type declaration name with package name and outer type declaration name.
+         */
+        private final String qualifiedName;
+
+        /**
+         * Depth of nesting of type declaration.
+         */
+        private final int depth;
+
+        /**
+         * Type declaration ast node.
+         */
+        private final DetailAST typeDeclAst;
+
+        /**
+         * A stack of type declaration's instance and static variables.
+         */
+        private final Deque<VariableDesc> instanceAndClassVarStack;
+
+        /**
+         * Create a new TypeDeclDesc instance.
+         *
+         * @param qualifiedName qualified name
+         * @param depth depth of nesting
+         * @param typeDeclAst type declaration ast node
+         */
+        /* package */ TypeDeclDesc(String qualifiedName, int depth,
+                DetailAST typeDeclAst) {
+            this.qualifiedName = qualifiedName;
+            this.depth = depth;
+            this.typeDeclAst = typeDeclAst;
+            instanceAndClassVarStack = new ArrayDeque<>();
+        }
+
+        /**
+         * Get the complete type declaration name i.e. type declaration name with package name
+         * and outer type declaration name.
+         *
+         * @return qualified class name
+         */
+        public String getQualifiedName() {
+            return qualifiedName;
+        }
+
+        /**
+         * Get the depth of type declaration.
+         *
+         * @return the depth of nesting of type declaration
+         */
+        public int getDepth() {
+            return depth;
+        }
+
+        /**
+         * Get the type declaration ast node.
+         *
+         * @return ast node of the type declaration
+         */
+        public DetailAST getTypeDeclAst() {
+            return typeDeclAst;
+        }
+
+        /**
+         * Get the copy of variables in instanceAndClassVar stack with updated scope.
+         *
+         * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
+         * @return copy of variables in instanceAndClassVar stack with updated scope.
+         */
+        public Deque<VariableDesc> getUpdatedCopyOfVarStack(DetailAST literalNewAst) {
+            final DetailAST updatedScope = literalNewAst.getLastChild();
+            final Deque<VariableDesc> instAndClassVarDeque = new ArrayDeque<>();
+            instanceAndClassVarStack.forEach(instVar -> {
+                final VariableDesc variableDesc = new VariableDesc(instVar.getName(),
+                        instVar.getTypeAst(), updatedScope);
+                variableDesc.registerAsInstOrClassVar();
+                instAndClassVarDeque.push(variableDesc);
+            });
+            return instAndClassVarDeque;
+        }
+
+        /**
+         * Add an instance variable or class variable to the stack.
+         *
+         * @param variableDesc variable to be added
+         */
+        public void addInstOrClassVar(VariableDesc variableDesc) {
+            instanceAndClassVarStack.push(variableDesc);
+        }
+    }
+}


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedLocalVariableCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedLocalVariableCheckTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnusedLocalVariableCheckTest.java
@@ -0,0 +1,350 @@
+////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code for adherence to a set of rules.
+// Copyright (C) 2001-2022 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.UnusedLocalVariableCheck.MSG_UNUSED_LOCAL_VARIABLE;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+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 UnusedLocalVariableCheckTest extends AbstractModuleTestSupport {
+
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable";
+    }
+
+    @Test
+    public void testGetRequiredTokens() {
+        final UnusedLocalVariableCheck checkObj =
+                new UnusedLocalVariableCheck();
+        final int[] actual = checkObj.getRequiredTokens();
+        final int[] expected = {
+            TokenTypes.DOT,
+            TokenTypes.VARIABLE_DEF,
+            TokenTypes.IDENT,
+            TokenTypes.SLIST,
+            TokenTypes.LITERAL_FOR,
+            TokenTypes.OBJBLOCK,
+            TokenTypes.CLASS_DEF,
+            TokenTypes.INTERFACE_DEF,
+            TokenTypes.ANNOTATION_DEF,
+            TokenTypes.PACKAGE_DEF,
+            TokenTypes.LITERAL_NEW,
+            TokenTypes.METHOD_DEF,
+            TokenTypes.CTOR_DEF,
+            TokenTypes.STATIC_INIT,
+            TokenTypes.INSTANCE_INIT,
+            TokenTypes.COMPILATION_UNIT,
+            TokenTypes.LAMBDA,
+            TokenTypes.ENUM_DEF,
+            TokenTypes.RECORD_DEF,
+            TokenTypes.COMPACT_CTOR_DEF,
+        };
+        assertWithMessage("Required tokens are invalid")
+                .that(actual)
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testGetAcceptableTokens() {
+        final UnusedLocalVariableCheck typeParameterNameCheckObj =
+                new UnusedLocalVariableCheck();
+        final int[] actual = typeParameterNameCheckObj.getAcceptableTokens();
+        final int[] expected = {
+            TokenTypes.DOT,
+            TokenTypes.VARIABLE_DEF,
+            TokenTypes.IDENT,
+            TokenTypes.SLIST,
+            TokenTypes.LITERAL_FOR,
+            TokenTypes.OBJBLOCK,
+            TokenTypes.CLASS_DEF,
+            TokenTypes.INTERFACE_DEF,
+            TokenTypes.ANNOTATION_DEF,
+            TokenTypes.PACKAGE_DEF,
+            TokenTypes.LITERAL_NEW,
+            TokenTypes.METHOD_DEF,
+            TokenTypes.CTOR_DEF,
+            TokenTypes.STATIC_INIT,
+            TokenTypes.INSTANCE_INIT,
+            TokenTypes.COMPILATION_UNIT,
+            TokenTypes.LAMBDA,
+            TokenTypes.ENUM_DEF,
+            TokenTypes.RECORD_DEF,
+            TokenTypes.COMPACT_CTOR_DEF,
+        };
+        assertWithMessage("Acceptable tokens are invalid")
+                .that(actual)
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testUnusedLocalVariable() throws Exception {
+        final String[] expected = {
+            "27:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "sameName"),
+            "28:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+            "31:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "testInLambdas"),
+            "33:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "coding"),
+            "34:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE,
+                    "InputUnusedLocalVariable"),
+            "50:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+            "54:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "c"),
+            "65:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+            "67:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "c"),
+            "71:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "p"),
+            "81:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "f"),
+            "84:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "foo"),
+            "91:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariable.java"), expected);
+    }
+
+    @Test
+    public void testUnusedLocalVar2() throws Exception {
+        final String[] expected = {
+            "17:14: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "i"),
+            "19:14: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "j"),
+            "30:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "31:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+            "39:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+            "40:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "Test"),
+            "41:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj"),
+            "61:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "76:17: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariable2.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedLocalVarInAnonInnerClasses() throws Exception {
+        final String[] expected = {
+            "14:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "15:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "b"),
+            "17:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj"),
+            "22:17: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj"),
+            "32:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj2"),
+            "46:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "m"),
+            "47:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "l"),
+            "59:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "h"),
+            "62:17: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "v"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariableAnonInnerClasses.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedLocalVarGenericAnonInnerClasses() throws Exception {
+        final String[] expected = {
+            "13:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "l"),
+            "14:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj"),
+            "33:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "l"),
+            "34:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj2"),
+            "47:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "s"),
+            "67:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "variable"),
+            "69:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "anotherVar"),
+            "78:47: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj2"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariableGenericAnonInnerClasses.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedLocalVarDepthOfClasses() throws Exception {
+        final String[] expected = {
+            "28:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "r"),
+            "49:21: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "64:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "94:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "variable"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariableDepthOfClasses.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedLocalVarNestedClasses() throws Exception {
+        final String[] expected = {
+            "21:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "V"),
+            "23:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "S"),
+            "24:13: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "Q"),
+            "36:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "variable"),
+            "44:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "anotherVariable"),
+            "67:21: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "s"),
+            "68:21: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "n"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariableNestedClasses.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedLocalVarEnum() throws Exception {
+        final String[] expected = {
+            "22:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "50:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "77:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "a"),
+            "80:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "j"),
+            "92:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "d"),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputUnusedLocalVariableEnum.java"),
+                expected);
+    }
+
+    @Test
+    public void testUnusedLocalVarRecords() throws Exception {
+        final String[] expected = {
+            "16:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "var1"),
+            "25:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "var1"),
+            "26:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "obj"),
+            "36:9: " + getCheckMessage(MSG_UNUSED_LOCAL_VARIABLE, "var2"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputUnusedLocalVariableRecords.java"),
+                expected);
+    }
+
+    @Test
+    public void testClearStateVariables() throws Exception {
+        final UnusedLocalVariableCheck check = new UnusedLocalVariableCheck();
+        final Optional<DetailAST> methodDef = TestUtil.findTokenInAstByPredicate(
+                JavaParser.parseFile(
+                        new File(getPath("InputUnusedLocalVariable.java")),
+                        JavaParser.Options.WITHOUT_COMMENTS),
+                ast -> ast.getType() == TokenTypes.METHOD_DEF);
+        assertWithMessage("Ast should contain METHOD_DEF")
+                .that(methodDef.isPresent())
+                .isTrue();
+        final DetailAST variableDef = methodDef.get().getLastChild()
+                .findFirstToken(TokenTypes.VARIABLE_DEF);
+        assertWithMessage("State is not cleared on beginTree")
+                .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, variableDef,
+                        "variables",
+                        variables -> {
+                            return ((Collection<?>) variables).isEmpty();
+                        }))
+                .isTrue();
+    }
+
+    @Test
+    public void testClearStateClasses() throws Exception {
+        final UnusedLocalVariableCheck check = new UnusedLocalVariableCheck();
+        final Optional<DetailAST> classDef = TestUtil.findTokenInAstByPredicate(
+                JavaParser.parseFile(
+                        new File(getPath("InputUnusedLocalVariable.java")),
+                        JavaParser.Options.WITHOUT_COMMENTS),
+                ast -> ast.getType() == TokenTypes.CLASS_DEF);
+        assertWithMessage("Ast should contain CLASS_DEF")
+                .that(classDef.isPresent())
+                .isTrue();
+        final DetailAST classDefToken = classDef.get();
+        assertWithMessage("State is not cleared on beginTree")
+                .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, classDefToken,
+                        "typeDeclarations",
+                        typeDeclarations -> {
+                            return ((Collection<?>) typeDeclarations).isEmpty();
+                        }))
+                .isTrue();
+        assertWithMessage("State is not cleared on beginTree")
+                .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, classDefToken,
+                        "typeDeclAstToTypeDeclDesc",
+                        typeDeclAstToTypeDeclDesc -> {
+                            return ((Map<?, ?>) typeDeclAstToTypeDeclDesc).isEmpty();
+                        }))
+                .isTrue();
+        assertWithMessage("State is not cleared on beginTree")
+                .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, classDefToken,
+                        "depth",
+                        depth -> {
+                            return (int) depth == 0;
+                        }))
+                .isTrue();
+    }
+
+    @Test
+    public void testClearStateAnonInnerClass() throws Exception {
+        final UnusedLocalVariableCheck check = new UnusedLocalVariableCheck();
+        final DetailAST root = JavaParser.parseFile(
+                new File(getPath("InputUnusedLocalVariableAnonInnerClasses.java")),
+                JavaParser.Options.WITHOUT_COMMENTS);
+        // Not using TestUtil.isStatefulFieldClearedDuringBeginTree(..) because other
+        // nodes need to be processed first as those collections, maps are dependent on them.
+        final DetailAST classDefAst = root.findFirstToken(TokenTypes.CLASS_DEF);
+        final Optional<DetailAST> literalNew = TestUtil.findTokenInAstByPredicate(root,
+                ast -> ast.getType() == TokenTypes.LITERAL_NEW);
+        assertWithMessage("Ast should contain LITERAL_NEW")
+                .that(literalNew.isPresent())
+                .isTrue();
+        check.beginTree(root);
+        check.visitToken(classDefAst);
+        check.visitToken(literalNew.get());
+        check.beginTree(null);
+        final Predicate<Object> isClear = anonInnerAstToTypeDesc -> {
+            return ((Map<?, ?>) anonInnerAstToTypeDesc).isEmpty();
+        };
+        assertWithMessage("State is not cleared on beginTree")
+                .that(isClear.test(TestUtil.getInternalState(
+                        check, "anonInnerAstToTypeDeclDesc")))
+                .isTrue();
+        final Predicate<Object> isQueueClear = anonInnerClassHolders -> {
+            return ((Collection<?>) anonInnerClassHolders).isEmpty();
+        };
+        assertWithMessage("State is not cleared on beginTree")
+                .that(isQueueClear.test(TestUtil.getInternalState(
+                        check, "anonInnerClassHolders")))
+                .isTrue();
+    }
+
+    @Test
+    public void testClearStatePackageDef() throws Exception {
+        final UnusedLocalVariableCheck check = new UnusedLocalVariableCheck();
+        final Optional<DetailAST> packageDef = TestUtil.findTokenInAstByPredicate(
+                JavaParser.parseFile(
+                        new File(getPath("InputUnusedLocalVariable.java")),
+                        JavaParser.Options.WITHOUT_COMMENTS),
+                ast -> ast.getType() == TokenTypes.PACKAGE_DEF);
+        assertWithMessage("Ast should contain PACKAGE_DEF")
+                .that(packageDef.isPresent())
+                .isTrue();
+        final DetailAST packageDefToken = packageDef.get();
+        assertWithMessage("State is not cleared on beginTree")
+                .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, packageDefToken,
+                        "packageName",
+                        ""::equals))
+                .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
@@ -46,20 +46,20 @@ protected String getPackageLocation() {
 
     @Test
     public void test() {
-        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(198);
+        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(199);
     }
 
     @Test
     public void testDuplicatePackage() {
         assertThat(XmlMetaReader
                     .readAllModulesIncludingThirdPartyIfAny("com.puppycrawl.tools.checkstyle.meta"))
-                .hasSize(198);
+                .hasSize(199);
     }
 
     @Test
     public void testBadPackage() {
         assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny("DOES.NOT.EXIST"))
-                .hasSize(198);
+                .hasSize(199);
     }
 
     @Test
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableRecords.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableRecords.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableRecords.java
@@ -0,0 +1,53 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+import java.util.function.Predicate;
+
+//non-compiled with javac: Compilable with Java14
+public record InputUnusedLocalVariableRecords(int a, int b) {
+    public InputUnusedLocalVariableRecords {
+        int ab = 12; // ok
+        ab += a;
+        int var1 = 1; // violation
+        new nested() {
+            void method() {
+                var1 += 12;
+            }
+        };
+    }
+
+    static Predicate<Integer> obj = a -> {
+        int var1 = 1; // violation
+        nested obj = new nested() { // violation
+            void method() {
+                var1 += 12;
+            }
+        };
+        return a == 12;
+    };
+
+    public InputUnusedLocalVariableRecords(int a) {
+        this(a, -1);
+        int var2 = 12; // violation
+        inner obj = new InputUnusedLocalVariableRecords.inner() {
+            {
+                var2 += 12;
+            }
+        };
+        obj.getClass();
+    }
+
+
+    static class nested {
+        int var1 = 12;
+    }
+
+    class inner {
+        int var2 = 1;
+    }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariable.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariable.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariable.java
@@ -0,0 +1,116 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+import java.util.function.Predicate;
+
+public class InputUnusedLocalVariable {
+
+    int sameName = 20;
+
+    static int a = 12;
+
+    static int b = 12;
+
+    String s;
+
+    Obj obj = new Obj() {
+        int a = 12; // ok
+        int b = 12; // ok
+    };
+
+    private void sameName(int unusedParameter) {
+        int sameName = 10; // violation
+        int b = 12; // violation
+        b = 23;
+        this.sameName /= 2;
+        int testInLambdas = 0; // violation
+        testInLambdas = 12;
+        int coding = 0; // violation
+        int InputUnusedLocalVariable = 1; // violation
+        com.puppycrawl.tools.checkstyle.checks
+                .coding.unusedlocalvariable
+                .InputUnusedLocalVariable.testInLambdas(a);
+    }
+
+    public void testCallingMethods() {
+        int var = 21; // ok
+        sameName(var);
+        var = sameName;
+    }
+
+    public static void testInLambdas(int q) {
+        int test = 21; // ok
+        int isInOuterScope = 32; // ok
+        Predicate<Integer> foo = integer -> {
+            int b; // violation
+            int a = integer; // ok
+            b = integer;
+            boolean ans = a > 12; // ok
+            int c = 21; // violation
+            c = -isInOuterScope;
+            return ans;
+        };
+        foo.test(test);
+    }
+
+    public void testMultipleIdentifiers() {
+        int[] arr = {1};
+        int a = 21;
+        arr[0] = a;
+        Class<Integer> b = int.class; // violation
+        @SuppressWarnings("unused")
+        int c = 41; // violation
+    }
+
+    public void testChainedCalls() throws InterruptedException {
+        Obj p; // violation
+        Obj q = null; // ok
+        p = q.foo().p;
+        if (s instanceof String) {
+        }
+        Boolean r = false;
+        while (r.booleanValue() != true) {
+        }
+        int a = 13, b = 21;
+        q.getClass().wait(a, b);
+        int f = 12; // violation
+        Predicate<String> obj = InputUnusedLocalVariable::f;
+        obj.test("test");
+        int foo = 12; // violation
+        foo(q);
+    }
+
+    class SubClass extends InputUnusedLocalVariable {
+
+        void testSuperKeyword() {
+            int a = 12; // violation
+            a = super.a;
+        }
+    }
+
+    class Obj {
+        Obj p;
+
+        Obj foo() {
+            return new Obj();
+        }
+    }
+
+    Object foo(Object a) {
+        return a;
+    }
+
+    public static boolean f(String a) {
+
+        class k { // local class not to be added to "classes" stack
+            int a = 12;
+        }
+        return true;
+    }
+
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariable2.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariable2.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariable2.java
@@ -0,0 +1,107 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+public class InputUnusedLocalVariable2 {
+    int a = 12;
+
+    public void testStatementsWithoutSlistToken() {
+        int k = 1;
+        for (int i = 0; k < 1; i++) // violation
+            i++;
+        for (int j = 0; ; ) { // violation
+            j++;
+            break;
+        }
+        ArrayList<String> obj = new ArrayList<>();
+        for (String s : obj) {
+            System.out.println(s);
+        }
+    }
+
+    public void testChainedCallsWithNewKeyword() {
+        Object a; // violation
+        Object b; // violation
+        a = testDifferentAssignments(new c.b(new d.a()));
+        a = testDifferentAssignments(new b().new a());
+        b = testDifferentAssignments(new a.d());
+    }
+
+    static class Test implements a {
+        public void testMethodReference() {
+            int b = 12; // violation
+            int Test = 12; // violation
+            a obj = Test::new; // violation
+            Test ab = new Test();
+            Predicate<String> a = ab::b;
+            a.test("abc");
+        }
+
+        private boolean b(String s) {
+            return true;
+        }
+
+        public static boolean ba(String s) {
+            return true;
+        }
+
+        @Override
+        public void method() {
+        }
+    }
+
+    public Object testDifferentAssignments(Object obj) {
+        int a = 12; // violation
+        a = 13;
+        int b = 13; // ok
+        b /= 12;
+        int c = 1; // ok
+        c -= this.a;
+        int h = 1; // ok
+        h <<= 12;
+        return null;
+    }
+
+    public void testDoWhileLoops(int s) {
+        do {
+            int a = 12; // ok
+            do {
+                int b = 1; // violation
+                b = a;
+            }
+            while (s > 1);
+        }
+        while (s > 0);
+    }
+
+    interface a {
+        void method();
+
+        class d {
+        }
+    }
+
+    static class c {
+        static class b {
+            public b(Object a) {
+            }
+        }
+    }
+
+    static class d {
+        public static class a {
+        }
+    }
+
+    class b {
+        class a {
+        }
+    }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableAnonInnerClasses.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableAnonInnerClasses.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableAnonInnerClasses.java
@@ -0,0 +1,86 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+public class InputUnusedLocalVariableAnonInnerClasses {
+
+    static int a = 12, b = 13, c = 14;
+
+    public void testAnonymousInnerClass() {
+        int a = 12; // violation
+        int b = 12; // violation
+        int k = 14; // ok
+        Test obj = new Test() { // violation
+            int a = 2;
+
+            private void testSameName(int s) {
+                s = a + InputUnusedLocalVariable.a;
+                Test obj = new Test() { // violation
+                    int b = 1;
+
+                    private void testSameNameNested(int s) {
+                        s = b + InputUnusedLocalVariable.b + InputUnusedLocalVariable.a + a + k;
+                    }
+                };
+            }
+        };
+
+        Test obj2; // violation
+        obj2 = new Test() {
+            int a = 1;
+            int b = 1;
+            int c = 0; // ok
+
+            private void testSameName(int s) {
+                s = a + b + this.a + this.b + InputUnusedLocalVariableAnonInnerClasses.a
+                        + InputUnusedLocalVariableAnonInnerClasses.b;
+            }
+        };
+    }
+
+    {
+        int m = 12; // violation
+        int l = 2; // violation
+        d obj = new d() {
+            void method() {
+                m += l;
+            }
+        };
+        obj.getClass();
+    }
+
+    static {
+        int a = 1;
+        int x = 2;
+        int h = 3; // violation
+        m.Test obj = new m().new Test() {
+            void method() {
+                boolean v = // violation
+                        a == x == (h == 2);
+            }
+        };
+        obj.getClass();
+    }
+
+    class Test {
+        private int k = 1;
+    }
+
+    static class m {
+        public int x = 1;
+
+        class Test {
+            public int h = 12;
+        }
+    }
+
+    class d {
+        protected int m = 11;
+        int l = 14;
+    }
+
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableDepthOfClasses.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableDepthOfClasses.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableDepthOfClasses.java
@@ -0,0 +1,102 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+public class InputUnusedLocalVariableDepthOfClasses {
+
+    int variable = 1;
+
+    class k {
+        class n {
+        }
+    }
+
+    class n {
+        int r = 1;
+    }
+
+    class f {
+        class n {
+        }
+    }
+
+    void method() {
+        int r = 1; // violation
+        n obj = new n() {
+            void method() {
+                r += 12;
+            }
+        };
+        obj.getClass();
+    }
+
+    class r {
+        class k {
+            int p = 12;
+        }
+    }
+
+    static class p {
+        class r {
+            class k {
+                int a = 13;
+
+                void method() {
+                    int a = 1; // violation
+                    int p = 1;
+                    r.k obj = new r().new k() {
+                        void method() {
+                            a += p;
+                        }
+                    };
+                    obj.getClass();
+                }
+            }
+        }
+    }
+
+    class P {
+        void method() {
+            int a = 1; // violation
+            Q obj = new Q() {
+                void method() {
+                    a += 1;
+                }
+
+            };
+            obj.getClass();
+        }
+    }
+
+    class Q {
+        int a = 1;
+    }
+
+    class PR {
+        class P {
+            class Q {
+            }
+        }
+    }
+}
+
+class anotherClass {
+    class InputUnusedLocalVariableDepthOfClasses {
+    }
+}
+
+class Clazz {
+    void method() {
+        int variable = 1; // violation
+        InputUnusedLocalVariableDepthOfClasses obj = new InputUnusedLocalVariableDepthOfClasses() {
+            void method() {
+                variable += 1;
+            }
+        };
+        obj.getClass();
+    }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableEnum.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableEnum.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableEnum.java
@@ -0,0 +1,101 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
+public enum InputUnusedLocalVariableEnum {
+
+    CONST_1,
+    CONST_2(new a() {
+    }),
+    CONST_3(new a() {
+    });
+
+    InputUnusedLocalVariableEnum(Object obj) {
+        int a = 12; // violation
+        a = (int) obj;
+    }
+
+    InputUnusedLocalVariableEnum() {
+    }
+
+    public static class nestedClass {
+        int d = 12;
+    }
+
+    public class innerClass {
+        int k = 1;
+    }
+
+    public void testTryWithResources(int a) {
+        try (BufferedReader br =
+                     new BufferedReader(new FileReader("someFile.txt"))) { // ok
+            br.readLine();
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    static int a = 12;
+
+    public void testIncrementAndDecrementKinds() {
+        int a = 0; // violation
+        a = ++this.a;
+        a++;
+        a--;
+        int index = 1;
+        int[] arr = {1};
+        arr[--index] = 3;
+        int ind = 0;
+        testTryWithResources(--ind);
+        int k = 1;
+        if (++k > 12) {
+        }
+        int p = 2;
+        if ((++p - --p) > 21) {
+        }
+        int m = 2;
+        int h = ++m;
+        h += 1;
+    }
+
+    public void testBooleanExpressions() {
+        boolean b = false; // ok
+        if (!b) {
+        }
+        boolean b1 = true; // ok
+        if (b1) {
+        }
+        boolean a; // violation
+        if ((a = true) != false) {
+        }
+        boolean j; // violation
+        if (j = true) {
+        }
+        boolean k = true, l = false;
+        if (k && !l) {
+        }
+    }
+}
+
+class a {
+
+    void method() {
+        int d = 12; // violation
+        InputUnusedLocalVariableEnum.nestedClass obj =
+                new InputUnusedLocalVariableEnum.nestedClass() {
+                    void method() {
+                        d += 12;
+                    }
+                };
+        obj.getClass();
+    }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableGenericAnonInnerClasses.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableGenericAnonInnerClasses.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableGenericAnonInnerClasses.java
@@ -0,0 +1,99 @@
+/*
+UnusedLocalVariable
+
+
+*/
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+import java.util.function.Function;
+
+public class InputUnusedLocalVariableGenericAnonInnerClasses {
+
+    Function<Integer, Integer> obj = a -> {
+        int l = 12; // violation
+        testClass obj = new testClass() { // violation
+            void method() {
+                l += 1;
+            }
+        };
+        return a;
+    };
+
+    testClass obj2 = new testClass() {
+        int l = 13; // ok, instance var
+        testClass obj2 = new testClass() { // ok, instance var
+            void method() {
+                l += 1;
+            }
+        };
+    };
+
+    testClass obj3 = new testClass() {
+        void method() {
+            int l = 13; // violation
+            testClass obj2 = new testClass() { // violation
+                void method() {
+                    l += 1;
+                }
+            };
+        }
+    };
+}
+
+class testClass {
+    int l = 12;
+
+    void method2() {
+        int s = 12; // violation
+        int j = 13;
+        hoo k = () -> {
+            Integer.valueOf(j);
+            hoo obj2 = () -> {
+                testClass obj3 = new testClass() {
+                    void foo() {
+                        s += 12;
+                    }
+                };
+                obj3.getClass();
+            };
+            obj2.anotherMethod();
+        };
+        k.anotherMethod();
+    }
+
+    int s = 2;
+
+    void test() {
+        int variable = 1; // violation
+        int mainVar = 2; // ok
+        int anotherVar = 1; // violation
+        testingGenerics<Integer>.nested obj
+                = new testingGenerics<Integer>().new nested() {
+            void method() {
+                anotherVar += 1;
+            }
+        };
+        obj.getClass();
+
+        testingGenerics<Integer>.clazz<String>.nested obj2 // violation
+                = new testingGenerics<Integer>().new clazz<String>().new nested() {
+            void method() {
+                variable += 1;
+                Integer.valueOf(mainVar);
+            }
+        };
+    }
+}
+class testingGenerics<T> {
+    int mainVar = 2;
+
+    class clazz<T> {
+        class nested {
+            int variable = 1;
+        }
+    }
+
+    class nested {
+        int anotherVar = 2;
+    }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableNestedClasses.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableNestedClasses.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unusedlocalvariable/InputUnusedLocalVariableNestedClasses.java
@@ -0,0 +1,103 @@
+/*
+UnusedLocalVariable
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.coding.unusedlocalvariable;
+
+import java.lang.annotation.Annotation;
+
+public class InputUnusedLocalVariableNestedClasses {
+
+    public int V = 1;
+    private int G = 1;
+    protected int S = 1;
+    int Q = 1;
+
+    class AnotherClass {
+
+        void method() {
+            int V = 1; // violation
+            int G = 1;
+            int S = 1; // violation
+            int Q = 1; // violation
+            InputUnusedLocalVariableNestedClasses obj
+                    = new InputUnusedLocalVariableNestedClasses() {
+                void method() {
+                    Integer.valueOf(V + G + S + Q);
+                }
+            };
+            obj.getClass();
+        }
+    }
+
+    void method() {
+        int variable = 1; // violation
+        A.B obj = new A.B() {
+            void method() {
+                variable += 1;
+            }
+        };
+        obj.getClass();
+
+        int anotherVariable = 2; // violation
+        A.B.innerClass obj2 = new A.B().new innerClass() {
+            void method() {
+                anotherVariable += 1;
+            }
+        };
+        obj2.getClass();
+
+        InputUnusedLocalVariable obj3
+                = new InputUnusedLocalVariable() { // cross file checking not supported
+        };
+        obj3.getClass();
+    }
+
+    static class A {
+        static class B {
+            int variable = 1;
+
+            class innerClass {
+                protected int anotherVariable = 1;
+                public int k = 1;
+
+                void testInterfaces() {
+                    int s = 12; // violation
+                    int n = 13; // violation
+                    foo obj = new foo() {
+                        @Override
+                        public Class<? extends Annotation> annotationType() {
+                            Integer.valueOf(s);
+
+                            hoo obj = () -> {
+                                hoo anotherObj = new hoo() {
+                                    @Override
+                                    public void anotherMethod() {
+                                        Integer.valueOf(n);
+                                    }
+                                };
+                                anotherObj.getClass();
+                                String outsideAnonInner = "n is accessible by " + hoo.n;
+                                outsideAnonInner.isEmpty();
+                            };
+                            obj.getClass();
+                            return null;
+                        }
+                    };
+                    obj.getClass();
+                }
+            }
+        }
+    }
+}
+
+@interface foo {
+    int s = 2;
+}
+
+interface hoo {
+    int n = 12;
+    void anotherMethod();
+}