Repository: checkstyle/checkstyle

Problem statement:
Add Check Support for Java 21 Pattern Matching for Switch Syntax: New Check WhenShouldBeUsed
child of #14961: 

pattern matching for switch, allows more concise and readable code when dealing with various object types. This feature includes the `when` keyword to add conditions to case labels directly, improving readability.

I propose to Introduce a new check to enforce the use of the `when` keyword in switch case patterns to replace single if statements, in the case body, enhancing code readability and consistency.

---
**Config**
```XML
$ cat config.xml 
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <module name="TreeWalker">
        <module name="WhenShouldBeUsed"/>
    </module>
</module>
```
---
**Example**

```Java
void test(Object response) {
    switch (response) {
        case String s -> {
            if(s.length() > 80) {     // violation
                System.out.println("This string is too long");
            }
         }
        default -> System.out.println("Unknown type");
    }
     switch (response) {
        case String s when s.length() > 80 -> {  // ok
                System.out.println("This string is too long");
         }
        default -> System.out.println("Unknown type");
    }
}
```
**Expected Output**
```

$ java -jar checkstyle-10.19.0-all.jar -c config.xml Test.java 
Starting audit...
[ERROR] D:\Test.java:4:12: when can be used instead of single if statement. [WhenShouldBeUsed]
Audit done.
Checkstyle ends with 1 errors.
```


Hints:
Examples don't show but what if there is an else to the if or things besides the if statement? Are we going to print no violation?
Yes, no violation. IMO if we have more than one condition to check in the case body then a series of  `if elif else` will be more readable than using `when` However I don't have a strong opinion on this so I am not sure. what do you think?
please note that the sonarlint rule works when there is a single if inside the pattern body

```Java
        switch (response) {
            case String s -> {
                if (s.length() > 100) {
                    System.out.println("This string is too long");
                } else if (s.length() > 10) {
                    System.out.println("This string is long");
                } else if (s.length() > 5) {
                    System.out.println("This string is meduim");
                } else {
                    System.out.println("This string is short");
                }
            }
            default -> System.out.println("Unknown type");
        }
        // after fix
        switch (response) {
            case String s when s.length() > 100 -> {
                System.out.println("This string is too long");
            }
            case String s when s.length() > 10 -> {
                System.out.println("This string is long");
            }
            case String s when s.length() > 5 -> System.out.println("This string is meduim");
            case String s -> System.out.println("This string is short");
            default -> System.out.println("Unknown type");
        }
```
> if elif else will be more readable than using when However I don't have a strong opinion on this so I am not sure. what do you think?

I say what till after this check is implemented before we think about this. I would just like issue descriptions for new checks to be more robust so there is no surprises in PR. We should be communicating what we will validate and what we won't validate.

I am good with this check.
I am approving for single `if` statement only. Let's keep the first implementation simple.
Identified at https://github.com/checkstyle/checkstyle/pull/15113#discussion_r1663471696 ,

The bytecode when using `when` is nearly identical to just using an nested if statement inside the case.

Edit: There does seem to be a performance impact in the current bytecode I was generating when you have multiple pattern cases with different whens. It has to cast the same variable multiple times to do the when check after previous whens failed. Maybe future JREs will improve this.

Patch:
diff --git a/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionWhenShouldBeUsedTest.java b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionWhenShouldBeUsedTest.java
new file mode 100644
--- /dev/null
+++ b/src/it/java/org/checkstyle/suppressionxpathfilter/XpathRegressionWhenShouldBeUsedTest.java
@@ -0,0 +1,91 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.checkstyle.suppressionxpathfilter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
+import com.puppycrawl.tools.checkstyle.checks.coding.WhenShouldBeUsedCheck;
+
+public class XpathRegressionWhenShouldBeUsedTest
+        extends AbstractXpathTestSupport {
+
+    @Override
+    protected String getCheckName() {
+        return WhenShouldBeUsedCheck.class.getSimpleName();
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        final File fileToProcess =
+                new File(getNonCompilablePath(
+                        "InputXpathWhenShouldBeUsedSimple.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(WhenShouldBeUsedCheck.class);
+        final String[] expectedViolation = {
+            "7:13: " + getCheckMessage(WhenShouldBeUsedCheck.class,
+                    WhenShouldBeUsedCheck.MSG_KEY),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+               "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputXpathWhenShouldBeUsedSimple']]"
+               + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_SWITCH/SWITCH_RULE"
+               + "[./LITERAL_CASE/PATTERN_VARIABLE_DEF/IDENT[@text='s']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputXpathWhenShouldBeUsedSimple']]"
+                        + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/"
+                        + "LITERAL_SWITCH/SWITCH_RULE/LITERAL_CASE"
+        );
+        runVerifications(moduleConfig, fileToProcess, expectedViolation, expectedXpathQueries);
+    }
+
+    @Test
+    public void testNested() throws Exception {
+        final File fileToProcess =
+                new File(getNonCompilablePath(
+                        "InputXpathWhenShouldBeUsedNested.java"));
+
+        final DefaultConfiguration moduleConfig =
+                createModuleConfig(WhenShouldBeUsedCheck.class);
+        final String[] expectedViolation = {
+            "10:21: " + getCheckMessage(WhenShouldBeUsedCheck.class,
+                    WhenShouldBeUsedCheck.MSG_KEY),
+        };
+
+        final List<String> expectedXpathQueries = Arrays.asList(
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputXpathWhenShouldBeUsedNested']]"
+                + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/"
+                + "VARIABLE_DEF[./IDENT[@text='x']]/ASSIGN/EXPR/LITERAL_SWITCH"
+                + "/SWITCH_RULE/SLIST/"
+                + "LITERAL_SWITCH/SWITCH_RULE[./LITERAL_CASE/PATTERN_VARIABLE_DEF/"
+                + "IDENT[@text='_']]",
+                "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputXpathWhenShouldBeUsedNested']]"
+                + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/VARIABLE_DEF"
+                + "[./IDENT[@text='x']]/ASSIGN/EXPR/LITERAL_SWITCH/"
+                + "SWITCH_RULE/SLIST/"
+                + "LITERAL_SWITCH/SWITCH_RULE/LITERAL_CASE"
+        );
+        runVerifications(moduleConfig, fileToProcess, expectedViolation, expectedXpathQueries);
+    }
+}
diff --git a/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/whenshouldbeused/InputXpathWhenShouldBeUsedNested.java b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/whenshouldbeused/InputXpathWhenShouldBeUsedNested.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/whenshouldbeused/InputXpathWhenShouldBeUsedNested.java
@@ -0,0 +1,22 @@
+//non-compiled with javac: Compilable with Java21
+package org.checkstyle.suppressionxpathfilter.whenshouldbeused;
+
+public class InputXpathWhenShouldBeUsedNested {
+
+    void test(Object o, Object o2, int y) {
+        int x = switch (o) {
+            case String s -> {
+                switch (o2) {
+                    case Integer _ -> { // warn
+                       if (y == 0) {
+                           System.out.println(0);
+                       }
+                    }
+                    default -> {}
+                }
+                yield 3;
+            }
+            default -> 3;
+        };
+    }
+}
diff --git a/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/whenshouldbeused/InputXpathWhenShouldBeUsedSimple.java b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/whenshouldbeused/InputXpathWhenShouldBeUsedSimple.java
new file mode 100644
--- /dev/null
+++ b/src/it/resources-noncompilable/org/checkstyle/suppressionxpathfilter/whenshouldbeused/InputXpathWhenShouldBeUsedSimple.java
@@ -0,0 +1,15 @@
+//non-compiled with javac: Compilable with Java21
+package org.checkstyle.suppressionxpathfilter.whenshouldbeused;
+
+public class InputXpathWhenShouldBeUsedSimple {
+    void test(Object o) {
+        switch (o) {
+            case String s -> { // warn
+                if (s.isEmpty()) {
+                    System.out.println("empty string");
+                }
+            }
+            default -> {}
+        }
+    }
+ }
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
@@ -591,6 +591,8 @@ private static void fillChecksFromCodingPackage() {
                 BASE_PACKAGE + ".checks.coding.UnnecessarySemicolonInTryWithResourcesCheck");
         NAME_TO_FULL_MODULE_NAME.put("VariableDeclarationUsageDistanceCheck",
                 BASE_PACKAGE + ".checks.coding.VariableDeclarationUsageDistanceCheck");
+        NAME_TO_FULL_MODULE_NAME.put("WhenShouldBeUsed",
+                BASE_PACKAGE + ".checks.coding.WhenShouldBeUsedCheck");
         NAME_TO_FULL_MODULE_NAME.put("NoArrayTrailingCommaCheck",
                 BASE_PACKAGE + ".checks.coding.NoArrayTrailingCommaCheck");
         NAME_TO_FULL_MODULE_NAME.put("MatchXpathCheck",
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheck.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheck.java
@@ -0,0 +1,183 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.puppycrawl.tools.checkstyle.StatelessCheck;
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
+
+/**
+ * <p>
+ * Ensures that {@code when} is used instead of a single {@code if}
+ * statement inside a case block.
+ * </p>
+ * <p>
+ * Rationale: Java 21 has introduced enhancements for switch statements and expressions
+ * that allow the use of patterns in case labels. The {@code when} keyword is used to specify
+ * condition for a case label, also called as guarded case labels. This syntax is more readable
+ * and concise than the single {@code if} statement inside the pattern match block.
+ * </p>
+ * <p>
+ * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-14.html#jls-Guard">
+ * Java Language Specification</a> for more information about guarded case labels.
+ * </p>
+ * <p>
+ * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-14.html#jls-14.30">
+ * Java Language Specification</a> for more information about patterns.
+ * </p>
+ * <p>
+ * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
+ * </p>
+ * <p>
+ * Violation Message Keys:
+ * </p>
+ * <ul>
+ * <li>
+ * {@code when.should.be.used}
+ * </li>
+ * </ul>
+ *
+ * @since 10.18.0
+ */
+
+@StatelessCheck
+public class WhenShouldBeUsedCheck extends AbstractCheck {
+
+    /**
+     * A key is pointing to the warning message text in "messages.properties"
+     * file.
+     */
+    public static final String MSG_KEY = "when.should.be.used";
+
+    @Override
+    public int[] getDefaultTokens() {
+        return getRequiredTokens();
+    }
+
+    @Override
+    public int[] getAcceptableTokens() {
+        return getRequiredTokens();
+    }
+
+    @Override
+    public int[] getRequiredTokens() {
+        return new int[] {TokenTypes.LITERAL_CASE};
+    }
+
+    @Override
+    public void visitToken(DetailAST ast) {
+        final boolean hasPatternLabel = hasPatternLabel(ast);
+        final DetailAST statementList = getStatementList(ast);
+        // until https://github.com/checkstyle/checkstyle/issues/15270
+        final boolean isInSwitchRule = ast.getParent().getType() == TokenTypes.SWITCH_RULE;
+
+        if (hasPatternLabel && statementList != null && isInSwitchRule) {
+            final List<DetailAST> blockStatements = getBlockStatements(statementList);
+
+            final boolean hasAcceptableStatementsOnly = blockStatements.stream()
+                    .allMatch(WhenShouldBeUsedCheck::isAcceptableStatement);
+
+            final boolean hasSingleIfWithNoElse = blockStatements.stream()
+                    .filter(WhenShouldBeUsedCheck::isSingleIfWithNoElse)
+                    .count() == 1;
+
+            if (hasAcceptableStatementsOnly && hasSingleIfWithNoElse) {
+                log(ast, MSG_KEY);
+            }
+        }
+    }
+
+    /**
+     * Get the statement list token of the case block.
+     *
+     * @param caseAST the AST node representing {@code LITERAL_CASE}
+     * @return the AST node representing {@code SLIST} of the current case
+     */
+    private static DetailAST getStatementList(DetailAST caseAST) {
+        final DetailAST caseParent = caseAST.getParent();
+        return caseParent.findFirstToken(TokenTypes.SLIST);
+    }
+
+    /**
+     * Get all statements inside the case block.
+     *
+     * @param statementList the AST node representing {@code SLIST} of the current case
+     * @return statements inside the current case block
+     */
+    private static List<DetailAST> getBlockStatements(DetailAST statementList) {
+        final List<DetailAST> blockStatements = new ArrayList<>();
+        DetailAST ast = statementList.getFirstChild();
+        while (ast != null) {
+            blockStatements.add(ast);
+            ast = ast.getNextSibling();
+        }
+        return Collections.unmodifiableList(blockStatements);
+    }
+
+    /**
+     * Check if the statement is an acceptable statement inside the case block.
+     * If these statements are the only ones in the case block, this case
+     * can be considered a violation. If at least one of the statements
+     * is not acceptable, this case can not be a violation.
+     *
+     * @param ast the AST node representing the statement
+     * @return true if the statement is an acceptable statement, false otherwise
+     */
+    private static boolean isAcceptableStatement(DetailAST ast) {
+        final int[] acceptableChildrenOfSlist = {
+            TokenTypes.LITERAL_IF,
+            TokenTypes.LITERAL_BREAK,
+            TokenTypes.EMPTY_STAT,
+            TokenTypes.RCURLY,
+        };
+        return TokenUtil.isOfType(ast, acceptableChildrenOfSlist);
+    }
+
+    /**
+     * Check if the case block has a pattern variable definition
+     * or a record pattern definition.
+     *
+     * @param caseAST the AST node representing {@code LITERAL_CASE}
+     * @return true if the case block has a pattern label, false otherwise
+     */
+    private static boolean hasPatternLabel(DetailAST caseAST) {
+        return caseAST.findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF) != null
+                || caseAST.findFirstToken(TokenTypes.RECORD_PATTERN_DEF) != null
+                || caseAST.findFirstToken(TokenTypes.PATTERN_DEF) != null;
+    }
+
+    /**
+     * Check if the case block statement is a single if statement with no else branch.
+     *
+     * @param statement statement to check inside the current case block
+     * @return true if the statement is a single if statement with no else branch, false otherwise
+     */
+    private static boolean isSingleIfWithNoElse(DetailAST statement) {
+        return statement.getType() == TokenTypes.LITERAL_IF
+                && statement.findFirstToken(TokenTypes.LITERAL_ELSE) == null;
+    }
+
+}
diff --git a/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheckExamplesTest.java b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheckExamplesTest.java
new file mode 100644
--- /dev/null
+++ b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheckExamplesTest.java
@@ -0,0 +1,44 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import static com.puppycrawl.tools.checkstyle.checks.coding.WhenShouldBeUsedCheck.MSG_KEY;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractExamplesModuleTestSupport;
+
+public class WhenShouldBeUsedCheckExamplesTest
+    extends AbstractExamplesModuleTestSupport {
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused";
+    }
+
+    @Test
+    public void testExample1() throws Exception {
+        final String[] expected = {
+            "18:7: " + getCheckMessage(MSG_KEY),
+        };
+
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("Example1.java"), expected);
+    }
+}
diff --git a/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/Example1.java b/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/Example1.java
new file mode 100644
--- /dev/null
+++ b/src/xdocs-examples/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/Example1.java
@@ -0,0 +1,48 @@
+/*xml
+<module name="Checker">
+  <module name="TreeWalker">
+    <module name="WhenShouldBeUsed"/>
+  </module>
+</module>
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.whenshouldbeused;
+
+// xdoc section -- start
+public class Example1 {
+
+  void testNoGuard(Object o) {
+    switch (o) {
+      // violation below,  ''when' expression should be used*.'
+      case String s -> {
+        if (s.isEmpty()) {
+          System.out.println("empty string");
+        }
+      }
+      default -> {}
+    }
+
+    switch (o) {
+      // this example is ok, not a single if statement inside the case block
+      case String s -> {
+        System.out.println("String");
+        if (s.isEmpty()) {
+          System.out.println("but empty");
+        }
+      }
+      default -> {}
+    }
+  }
+
+  void testGuardedCaseLabel(Object o) {
+    switch (o) {
+      case String s when s.isEmpty() -> {
+        System.out.println("empty string");
+      }
+      default -> {}
+    }
+  }
+
+}
+// xdoc section -- end


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheckTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/WhenShouldBeUsedCheckTest.java
@@ -0,0 +1,93 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
+// Copyright (C) 2001-2024 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.puppycrawl.tools.checkstyle.checks.coding.WhenShouldBeUsedCheck.MSG_KEY;
+
+import org.junit.jupiter.api.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
+
+public class WhenShouldBeUsedCheckTest
+        extends AbstractModuleTestSupport {
+
+    @Override
+    protected String getPackageLocation() {
+        return "com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused";
+    }
+
+    @Test
+    public void testTokensNotNull() {
+        final WhenShouldBeUsedCheck check = new WhenShouldBeUsedCheck();
+        final int[] expected = {TokenTypes.LITERAL_CASE};
+
+        assertWithMessage("Acceptable tokens is not valid")
+            .that(check.getAcceptableTokens())
+            .isEqualTo(expected);
+        assertWithMessage("Default tokens is not valid")
+            .that(check.getDefaultTokens())
+            .isEqualTo(expected);
+        assertWithMessage("Required tokens is not valid")
+            .that(check.getRequiredTokens())
+            .isEqualTo(expected);
+    }
+
+    @Test
+    public void testSwitchStatements() throws Exception {
+        final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputWhenShouldBeUsedSwitchStatements.java"),
+            expected);
+    }
+
+    @Test
+    public void testSwitchRule() throws Exception {
+        final String[] expected = {
+            "13:13: " + getCheckMessage(MSG_KEY),
+            "18:13: " + getCheckMessage(MSG_KEY),
+            "82:13: " + getCheckMessage(MSG_KEY),
+            "96:13: " + getCheckMessage(MSG_KEY),
+            "105:13: " + getCheckMessage(MSG_KEY),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputWhenShouldBeUsedSwitchRule.java"),
+            expected);
+
+    }
+
+    @Test
+    public void testSwitchExpression() throws Exception {
+        final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputWhenShouldBeUsedSwitchExpression.java"),
+                expected);
+    }
+
+    @Test
+    public void testNonPatternsSwitch() throws Exception {
+        final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputWhenShouldBeUsedNonPatternsSwitch.java"),
+                expected);
+    }
+}
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/meta/XmlMetaReaderTest.java
@@ -40,20 +40,20 @@ protected String getPackageLocation() {
 
     @Test
     public void test() {
-        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(202);
+        assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny()).hasSize(203);
     }
 
     @Test
     public void testDuplicatePackage() {
         assertThat(XmlMetaReader
                     .readAllModulesIncludingThirdPartyIfAny("com.puppycrawl.tools.checkstyle.meta"))
-                .hasSize(202);
+                .hasSize(203);
     }
 
     @Test
     public void testBadPackage() {
         assertThat(XmlMetaReader.readAllModulesIncludingThirdPartyIfAny("DOES.NOT.EXIST"))
-                .hasSize(202);
+                .hasSize(203);
     }
 
     @Test
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedNonPatternsSwitch.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedNonPatternsSwitch.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedNonPatternsSwitch.java
@@ -0,0 +1,67 @@
+/*
+WhenShouldBeUsed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.whenshouldbeused;
+
+public class InputWhenShouldBeUsedNonPatternsSwitch {
+    void test(char c, int x) {
+        switch (x) {
+            case 1  :   // ok, guard is allowed after patterns only
+                if (x == 3) {
+                }
+                break;
+            case 4: {}
+        }
+        switch (x) {
+            case 1 :
+                if (x == 0) {
+                }
+            case 4:
+        }
+
+        switch (c) {
+            case 'a'  -> {
+                if (x == 0) {}
+            }
+        }
+
+        E e = E.A;
+        switch (e) {
+            case A : {
+                if (x == 0) {}
+            }
+            case B : {
+                if (x == 2) {}
+            }
+        }
+
+         switch (e) {
+            case A -> {
+                if (x == 0) {}
+            }
+            case B -> {
+                if (x == 5) {}
+            }
+        }
+
+        String s = "mahfouz";
+        switch (s) {
+            case  "a" :
+            case "b": {
+                if (x == 0) {}
+            }
+        }
+
+        switch (s) {
+            case  "a" , "b" -> {
+                if (x == 0) {}
+            }
+        }
+    }
+    enum E {
+        A, B
+    }
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchExpression.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchExpression.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchExpression.java
@@ -0,0 +1,107 @@
+/*
+WhenShouldBeUsed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.whenshouldbeused;
+
+public class InputWhenShouldBeUsedSwitchExpression {
+
+    void test(Object j, int x) {
+        int a = switch (j) {
+            case Point(int _, int y):
+                if (y == 0) {
+                    yield 0;
+                }
+            case String _: {if (x == 0)  yield 4;}
+            case Integer _: if (x == 0)  yield 4;
+            default: { yield 3;}
+        };
+        int b = switch (j) {
+            case Point(int _, int y): {
+                if (y == 0) {
+                    yield 0;
+                }
+            }
+            case ColoredPoint(Point(_, _), String _) : {
+                if (x == 0) yield 4;
+            }
+            default: { yield 3;}
+        };
+        int bb = switch (j) {
+            case Point(int _, int y) when y == 0:  {
+                yield 0;
+            }
+            case ColoredPoint(Point(_, _), String _) when x == 0: {
+                yield 4;
+            }
+            default: { yield 3;}
+        };
+
+        int d = switch (j) {
+            case String s: // ok, not a single if
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                    yield 0;
+                }
+                if (!s.isEmpty())
+                      yield 1;
+            default: { yield 3;}
+        };
+
+        int f = switch (j) {
+            case Point(int _, int y) -> {
+                if (y == 0) {
+                    yield 0;
+                }
+                yield 3;
+            }
+            case ColoredPoint(Point(int xx, int y), String color) -> {
+                if (y == 0 && xx == 0 && color.equals("red")) {
+                    yield 1;
+                }
+                System.out.println("red point at origin");
+                yield 4;
+            }
+            default -> 3;
+        };
+
+        int m = switch (j) {
+            case Point _:
+            case ColoredPoint _:
+                if (x == 0) {
+                    System.out.println("Integer, String");
+                    yield 4;
+                }
+            default: {yield 3;}
+        };
+
+        int mm = switch (j) {
+            case Point _ when x == 0:
+            case ColoredPoint _ when x == 0:
+                    System.out.println("Integer, String");
+                    yield 4;
+            default: {yield 3;}
+        };
+
+        int zz = switch (j) {
+            case Point _ , ColoredPoint _ when x == 0:
+                    System.out.println("Integer, String");
+                    yield 4;
+            default: {yield 3;}
+        };
+
+        int z = switch (j) {
+            case Point _ , ColoredPoint _ -> {
+                if (x == 0) {
+                    yield 4;
+                }
+                yield 0;
+            }
+             default -> 3;
+        };
+    }
+    record ColoredPoint(Point p , String color){}
+    record Point(int x, int y) {}
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchRule.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchRule.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchRule.java
@@ -0,0 +1,118 @@
+/*
+WhenShouldBeUsed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.whenshouldbeused;
+
+public class InputWhenShouldBeUsedSwitchRule {
+
+    void test(Object j, int x) {
+        switch (j) {
+            case String s -> { // violation, ''when' expression should be used*.'
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+            }
+            case Integer i -> { // violation, ''when' expression should be used*.'
+                if (x == 0) System.out.println("Integer");
+            }
+            default -> {}
+        }
+        switch (j) {
+            case String s when s.isEmpty() -> {
+                System.out.println("empty String");
+            }
+            case Integer i when x == 0 -> {
+                System.out.println("Integer");
+            }
+            default -> {}
+        }
+        switch (j) {
+            case String s -> {
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+                if (!s.isEmpty())
+                    System.out.println("Non empty String");
+            }
+            case Integer i -> {}
+            default -> {}
+        }
+        switch (j) {
+            case String s -> {   // ok, not single if, there is else if
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                } else if (!s.isEmpty())
+                    System.out.println("Non empty String");
+            }
+            default -> {}
+        }
+        switch (j) {
+            case String s -> {
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                } else
+                    System.out.println("Non empty String");
+            }
+            case Integer i -> {}
+            default -> {}
+        }
+         switch (j) {
+            case String s -> // ok, not a single if there is another statement
+            {
+                int y = get();
+                if (s.isEmpty() && y == 0) {
+                    System.out.println("empty String");
+                }
+            }
+            default -> {}
+        }
+         switch (j) {
+            case String s ->  {// ok, not a single if there is another statement
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+                System.out.println("A String");
+            }
+            default -> {}
+        }
+        switch (j) {
+            case Integer _, String _ -> {
+                // violation above, ''when' expression should be used*.'
+                if (x == 0) {
+                    System.out.println("Integer, String");
+                }
+            }
+            default -> {}
+        }
+        switch (j) {
+            case Integer _, String _ when x == 0 ->
+                System.out.println("Integer, String");
+            default -> {}
+        }
+        switch (j) {
+            case Point(int a,_) ->  { // violation, ''when' expression should be used*.'
+                if (a == 0) {}
+            }
+            default -> {}
+        }
+    }
+    void testGuardedCases(Object obj, int x) {
+        switch (obj) {
+            // violation below, ''when' expression should be used*.'
+            case Integer i when i == 0 -> {
+                if (x == 0) System.out.println("Integer");
+            }
+            default -> {}
+        }
+         switch (obj) {
+            case Integer i when i == 0 && x == 0 -> {
+            }
+            default -> {}
+        }
+    }
+    int get() { return 0; }
+    record Point(int x, int y) {}
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchStatements.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchStatements.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/whenshouldbeused/InputWhenShouldBeUsedSwitchStatements.java
@@ -0,0 +1,119 @@
+/*
+WhenShouldBeUsed
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.whenshouldbeused;
+
+public class InputWhenShouldBeUsedSwitchStatements {
+
+    void test(Object j, int x) {
+        switch (j) {
+            case String s:
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+                break;
+            case Integer i: if (x == 0) System.out.println("Integer"); break;
+            default: {}
+        }
+        switch (j) {
+            case String s: {
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                } break;
+            }
+            case Integer i : {
+                if (x == 0) System.out.println("Integer"); break;
+            }
+            default: {}
+        }
+        switch (j) {
+            case String s when s.isEmpty():
+                     System.out.println("empty String");
+                     break;
+            case Integer i when x == 0: System.out.println("Integer"); break;
+            default: {}
+        }
+
+        switch (j) {
+            case String s: // ok, not a single if
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+                if (!s.isEmpty())
+                      System.out.println("Non empty String");
+                break;
+            case Integer i: break;
+            default: {}
+        }
+        switch (j) {
+            case String s:  // ok, not single if, there is else if
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+                else if (!s.isEmpty())
+                      System.out.println("Non empty String");
+                break;
+            case Integer i: break;
+            default: {}
+        }
+        switch (j) {
+            case String s: {
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                } else
+                    System.out.println("Non empty String");
+                break;
+            }
+            case Integer i: break;
+            default: {}
+        }
+         switch (j) {
+            case String s: // ok, not a single if there is another statement
+                int y = get();
+                if (s.isEmpty() && y == 0) {
+                    System.out.println("empty String");
+                }
+                break;
+            case Integer i: break;
+            default: {}
+        }
+         switch (j) {
+            case String s: // ok, not a single if there is another statement
+                if (s.isEmpty()) {
+                    System.out.println("empty String");
+                }
+                System.out.println("A String");
+                break;
+            case Integer i: break;
+            default: {}
+        }
+        switch (j) {
+            case Integer _:
+            case String _:
+                if (x == 0) {
+                    System.out.println("Integer, String");
+                }
+            default: {}
+        }
+        switch (j) {
+            case Integer _:
+            case String _: {
+                if (x == 0) {
+                    System.out.println("Integer, String");
+                }
+            }
+            default: {}
+        }
+        switch (j) {
+            case Integer _ when x == 0:
+            case String _ when x == 0: {
+                    System.out.println("Integer, String");
+            }
+            default: {}
+        }
+    }
+    int get() { return 0; }
+}