Pattern Matching with switch
         A switch expression or statement
        transfers control to one of several statements or expressions, depending on the value of its
        selector expression. The selector expression can be any reference or primitive
        type.
               
Also, case labels can have patterns. Consequently, a
                switch expression or statement can test whether its selector
                expression matches a pattern, which offers more flexibility and expressiveness compared
                to testing whether its selector expression is exactly equal to a constant.
               
For background information about pattern matching for
                switch expressions and statements, see JEP 441.
               
Consider the following code that calculates the perimeter of certain shapes from the section Pattern Matching with instanceof:
interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
// ...
public static double getPerimeter(Shape s) throws IllegalArgumentException {
    if (s instanceof Rectangle r) {
        return 2 * r.length() + 2 * r.width();
    } else if (s instanceof Circle c) {
        return 2 * c.radius() * Math.PI;
    } else {
        throw new IllegalArgumentException("Unrecognized shape");
    }
}You can rewrite this code to use a pattern switch expression
            as follows:
               
public static double getPerimeter(Shape s) throws IllegalArgumentException {
    return switch (s) {
        case Rectangle r ->
            2 * r.length() + 2 * r.width();
        case Circle c ->
            2 * c.radius() * Math.PI;
        default ->
            throw new IllegalArgumentException("Unrecognized shape");
    };
}The following example uses a switch statement instead of a
                switch expression:
               
public static double getPerimeter(Shape s) throws IllegalArgumentException {
    switch (s) {
        case Rectangle r:
            return 2 * r.length() + 2 * r.width();
        case Circle c:
            return 2 * c.radius() * Math.PI;
        default:
            throw new IllegalArgumentException("Unrecognized shape");
    }
}When Clauses
You can add a Boolean expression right after a pattern label with a
                when clause. This is called a guarded pattern label. The
            Boolean expression in the when clause is called a guard. A value
            matches a guarded pattern label if it matches the pattern and, if so, the guard also
            evaluates to true. Consider the following example:
                  
static void test(Object obj) {
    switch (obj) {
        case String s:
            if (s.length() == 1) {
                System.out.println("Short: " + s);
            } else {
                System.out.println(s);
            }
            break;
        default:
            System.out.println("Not a string");
    }
}You can move the Boolean expression s.length == 1 right
            after the case label with a when clause: 
                  
static void test(Object obj) {
    switch (obj) {
        case String s when s.length() == 1 -> System.out.println("Short: " + s);
        case String s                      -> System.out.println(s);
        default                            -> System.out.println("Not a string");
    }
}The first pattern label (which is a guarded pattern label) matches if
                obj is both a String and of length 1. The second
            patten label matches if obj is a String of a different
            length.
                  
A guarded patten label has the form p
                when e where p is a pattern and e is a
            Boolean expression. The scope of any pattern variable declared in p includes e.
                  
The following example also demonstrates when clauses. It uses a primitive
            type, double, for its switch expression's selector
            expression:
                  
String doubleToRating(double rating) {
    return switch(rating) {
        case 0d -> "0 stars";
        case double d when d > 0d && d < 2.5d
            -> d + " is not good";
        case double d when d >= 2.5f && d < 5d
            -> d + " is better";
        case 5d -> "5 stars";
        default -> "Invalid rating";
    };
}Note:
Guards containing primitive types is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.See JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) for additional information.
The following example uses a long for its selector expression:
                  
void bigNumbers(long v) {
    switch (v) {
        case long x when x < 1_000_000L ->
            System.out.println("Less than a million");
        case long x when x < 1_000_000_000L ->
            System.out.println("Less than a billion");
        case long x when x < 1_000_000_000_000L ->
            System.out.println("Less than a trillion");
        case long x when x < 1_000_000_000_000_000L ->
            System.out.println("Less than a quadrillion");
        default -> System.out.println("At least a quadrillion");    
    }
}Pattern Label Dominance
It's possible that many pattern labels could match the value of the
        selector expression. To help predictability, the labels are tested in the order that they
        appear in the switch block. In addition, the compiler raises an error if a
        pattern label can never match because a preceding one will always match first. The following
        example results in a compile-time error:
                  
static void error(Object obj) {
    switch(obj) {
        case CharSequence cs ->
            System.out.println("A sequence of length " + cs.length());
        // error: this case label is dominated by a preceding case label
        case String s -> 
            System.out.println("A string: " + s);
        default -> { break; }
    }
}The first pattern label case CharSequence cs
                     dominates the second pattern label case String s because every
            value that matches the pattern String s also matches the pattern
                CharSequence cs but not the other way around. It's because
                String is a subtype of CharSequence.
                  
A pattern label can dominate a constant label. These examples cause compile-time errors:
static void error2(Integer value) {
    switch(value) {
        case Integer i ->
            System.out.println("Integer: " + i);
        // Compile-time errors for both cases -1 and 1:
        // this case label is dominated by a preceding case label
        case -1, 1 ->        
            System.out.println("The number 42");
        default -> { break; }
    }
}
// ...
    
enum Color { RED, GREEN, BLUE; }
// ...
    
static void error3(Color value) {
    switch(value) {
        case Color c ->
            System.out.println("Color: " + c);
        // error: this case label is dominated by a preceding case label   
        case RED -> 
            System.out.println("The color red");
    }        
}Note:
Guarded pattern labels don't dominate constant labels. For example:static void testInteger(Integer value) {
    switch(value) {
        case Integer i when i > 0 ->
            System.out.println("Positive integer");
        case 1 -> 
            System.out.println("Value is 1");
        case -1 -> 
            System.out.println("Value is -1");
        case Integer i ->
            System.out.println("An integer");
    }
} Although the value 1 matches both the guarded pattern
                label case Integer i when i > 0 and the constant label
                    case 1, the guarded pattern label doesn't dominate the constant
                label. Guarded patterns aren't checked for dominance because they're generally
                undecidable. Consequently, you should order your case labels so that constant labels
                appear first, followed by guarded pattern labels, and then followed by nonguarded
                pattern
            labels:
                     
static void testIntegerBetter(Integer value) {
    switch(value) {
        case 1 -> 
            System.out.println("Value is 1");
        case -1 -> 
            System.out.println("Value is -1");
        case Integer i when i > 0 ->
            System.out.println("Positive integer");
        case Integer i ->
            System.out.println("An integer");
    }
}Scope of Pattern Variables and switch
switch expression or statement, the scope of a
            pattern variable declared in a case label includes the
            following:
                     
                  - The whenclause of thecaselabel:static void test(Object obj) { switch (obj) { case Character c when c.charValue() == 7: System.out.println("Ding!"); break; default: break; } } }The scope of pattern variable cincludes thewhenclause of thecaselabel that contains the declaration ofc.
- 
                        
                        The expression, block, or throwstatement that appears to the right of the arrow of thecaselabel:static void test(Object obj) { switch (obj) { case Character c -> { if (c.charValue() == 7) { System.out.println("Ding!"); } System.out.println("Character, value " + c.charValue()); } case Integer i -> System.out.println("Integer: " + i); default -> { break; } } }The scope of pattern variable cincludes the block to the right ofcase Character c ->. The scope of pattern variableiincludes theprintlnstatement to the right ofcase Integer i ->.
- 
                        
                        The switch-labeled statement group of acaselabel:static void test(Object obj) { switch (obj) { case Character c: if (c.charValue() == 7) { System.out.print("Ding "); } if (c.charValue() == 9) { System.out.print("Tab "); } System.out.println("character, value " + c.charValue()); default: // You cannot use the pattern variable c here: break; } }The scope of pattern variable cincludes thecase Character cstatement group: the twoifstatements and theprintlnstatement that follows them. The scope doesn't include thedefaultstatement group even though theswitchstatement can execute thecase Character cstatement group, fall through thedefaultlabel, and then execute thedefaultstatement group.Note: You will get a compile-time error if it's possible to fall through a case label that declares a pattern variable. The following example doesn't compile:static void test(Object obj) { switch (obj) { case Character c: if (c.charValue() == 7) { System.out.print("Ding "); } if (c.charValue() == 9) { System.out.print("Tab "); } System.out.println("character"); case Integer i: // Compile-time error System.out.println("An integer " + i); default: System.out.println("Neither character nor integer"); } }If this code were allowed, and the value of the selector expression, obj, were aCharacter, then theswitchstatement can execute thecase Character cstatement group and then fall through thecase Integer ilabel, where the pattern variableiwould have not been initialized.See Scope of Pattern Variables and instanceof for more examples of where you can use a pattern variable. 
Null case Labels
switch expressions and switch
            statements used to throw a NullPointerException if the value of the
            selector expression is null. Currently, to add more flexibility, a
                null case label is available:
                     
                  static void test(Object obj) {
    switch (obj) {
        case null     -> System.out.println("null!");
        case String s -> System.out.println("String");
        default       -> System.out.println("Something else");
    }
}This example prints null! when obj is
                null instead of throwing a
            NullPointerException.
                  
You may not combine a null case label with anything but a
                default case label. The following generates a compiler error:
                  
static void testStringOrNull(Object obj) {
    switch (obj) {
        // error: invalid case label combination
        case null, String s -> System.out.println("String: " + s);
        default             -> System.out.println("Something else");
    }
}However, the following compiles:
static void testStringOrNull(Object obj) {
    switch (obj) {
        case String s       -> System.out.println("String: " + s);
        case null, default  -> System.out.println("null or not a string");
    }
}If a selector expression evaluates to null and the
                switch block does not have null case label, then a
                NullPointerException is thrown as normal. Consider the following
                switch statement:
                  
String s = null;
switch (s) {
    case Object obj -> System.out.println("This doesn't match null");
    // No null label; NullPointerException is thrown
    //     if s is null
}Although the pattern label case Object obj matches objects
            of type String, this example throws a
                NullPointerException. The selector expression evaluates to
                null, and the switch expression doesn't contain a
                null case label.