Pattern Matching for switch Expressions and Statements
A switch statement transfers control to one of several
        statements or expressions, depending on the value of its selector expression. In earlier
        releases, the selector expression must evaluate to a number, string or enum
        constant, and case labels must be constants. However, in this release, the selector
        expression can be of any type, and case labels can have patterns.
        Consequently, a switch statement or expression 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.
               
Note:
This is a preview feature, which is a feature whose design, specification, and implementation are complete, but is not permanent, which means that the 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.
For background information about pattern matching for
                    switch expressions and statements, see JEP 420.
                  
Consider the following code that calculates the perimeter of certain shapes from the section Pattern Matching for instanceof:
interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
...
    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        if (shape instanceof Rectangle r) {
            return 2 * r.length() + 2 * r.width();
        } else if (shape 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 shape) throws IllegalArgumentException {
        return switch (shape) {
            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 shape) throws IllegalArgumentException {
        switch (shape) {
            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");
        }
    }Selector Expression Type
The type of a selector expression can either be an integral primitive
                type or any reference type (such as in the previous examples). The following
                    switch expression matches the selector expression
                    obj with type patterns that involve a class type, an enum type,
                a record type, and an array type:
                  
record Point(int x, int y) { }
enum Color { RED, GREEN, BLUE; }
...
    static void typeTester(Object obj) {
        switch (obj) {
            case null     -> System.out.println("null");
            case String s -> System.out.println("String");
            case Color c  -> System.out.println("Color with " + c.values().length + " values");
            case Point p  -> System.out.println("Record class: " + p.toString());
            case int[] ia -> System.out.println("Array of int values of length" + ia.length);
            default       -> System.out.println("Something else");
        }
    }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());
            case String s -> // error: this case label is dominated by a preceding case label
                System.out.println("A string: " + s);
            default ->
                throw new IllegalStateException("Invalid argument");  
        }
    }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);
            case -1, 1 -> // Compile-time errors for both cases -1 and 1:
                          // this case label is dominated by a preceding case label       
                System.out.println("The number 42");
            default ->
                throw new IllegalStateException("Invalid argument");
        }
    }
    
    enum Color { RED, GREEN, BLUE; }
    
    static void error3(Color value) {
        switch(value) {
            case Color c ->
                System.out.println("Color: " + c);
            case RED -> // error: this case label is dominated by a preceding case label
                System.out.println("The color red");
        }        
    }A guarded pattern label (see Guarded Patterns) can also dominate a constant label:
    static void error4(Integer value) {
        switch(value) {
            case Integer i && i > 0 ->
                System.out.println("Positive integer");
            case -1, 1 -> // Compile-time errors for both cases -1 and 1:
                          // this case label is dominated by a preceding case label
                System.out.println("Value is 1 or -1");
            default ->
                throw new IllegalStateException("Invalid argument");
        }
    }    To resolve these compiler errors related to dominance, ensure that constant labels appear before guarded pattern labels, which must appear before non-guarded type pattern labels:
    static void checkIntegers(Integer value) {
        switch(value) {
            case -1, 1 -> // Constant labels
                System.out.println("Value is 1 or -1");
            case Integer i && i > 0 -> // Guarded pattern label
                System.out.println("Positive integer");
            case Integer i -> // Non-guarded type pattern label
                System.out.println("Neither positive, 1, nor -1");
        }
    }There are two labels that match all values: the default
                label and a total type pattern (see Null-Matching case Labels). You can't have more than one of these two labels in a
                    switch block.
                  
Type Coverage in switch Expressions and Statements
As described in Switch Expressions, the switch blocks of switch
                expressions and switch statements (which use pattern or
                    null labels) must be exhaustive, which means that for all
                possible values, there must be a matching switch label. The
                following switch expression is not exhaustive and generates a
                compile-time error. Its type coverage consists of the subtypes of
                    String or Integer, which doesn't include the
                type of the selector expression, Object:
                  
    static int coverage(Object obj) {
        return switch (obj) {         // Error - not exhaustive
            case String s  -> s.length();
            case Integer i -> i;
        };
    }However, the type coverage of the case label default is
                all types, so the following example compiles:
                  
    static int coverage(Object obj) {
        return switch (obj) { 
            case String s  -> s.length();
            case Integer i -> i;
            default        -> 0;
        };
    }The compiler takes into account whether the type of a selector
                expression is a sealed class. The following switch expression
                compiles. It doesn't need a default case label because its type
                coverage is the classes A, B, and
                    C, which are the only permitted subclasses of
                    S, the type of the selector expression:
                  
sealed interface S permits A, B, C { }
final class A implements S { }
final class B implements S { }
record C(int i) implements S { }  // Implicitly final
...
    static int testSealedCoverage(S s) {
        return switch (s) {
            case A a -> 1;
            case B b -> 2;
            case C c -> 3;
        };
    }The compiler can also determine the type coverage of a switch
                expression or statement if the type of its selector expression is a generic sealed
                class. The following example compiles. The only permitted subclasses of interface
                    I are classes A and B.
                However, because the selector expression is of type I<Integer>,
                the switch block requires only class B in its type
                coverage to be exhaustive:
                  
    sealed interface I<T> permits A, B {}
    final class A<X> implements I<String> {}
    final class B<Y> implements I<Y> {}
    static int testGenericSealedExhaustive(I<Integer> i) {
        return switch (i) {
        // Exhaustive as no A case possible!  
            case B<Integer> bi -> 42;
        };
    }Scope of Pattern Variable Declarations
As described in the section Pattern Matching for instanceof, the scope of a pattern variable is the places where the program
                can reach only if the instanceof operator is
                true:
                  
    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        if (shape instanceof Rectangle s) {
            // You can use the pattern variable s of type Rectangle here.
        } else if (shape instanceof Circle s) {
            // You can use the pattern variable s of type Circle here
            // but not the pattern variable s of type Rectangle.
        } else {
            // You cannot use either pattern variable here.
        }
    }In a switch expression, you can use a pattern variable
                inside the expression, block, or throw statement that appears right
                of the arrow. For example:
                  
    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 ->
                throw new IllegalStateException("Invalid argument"); 
        }
    }The scope of pattern variable c is the block to the
                right of case Character c ->. The scope of pattern variable
                    i is the println statement to the right of
                    case Integer I ->.
                  
In a switch statement, you can use a case label's
                pattern variable in its switch-labeled statement group. However,
                you can't use it in any other switch-labeled statement group, even
                if the program flow can fall through a default statement group. For
                example:
                  
    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:
                throw new IllegalStateException("Invalid argument");
        }
    }The scope of pattern variable c consists of the
                    case Character c statement group: the two if
                statements and the println statement that follows them. The scope
                doesn't include the default statement group even though the
                    switch statement can execute the case Character
                    c statement group, fall through the default case
                label, and then execute the default  statement group. 
                  
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, was a Character, then the
                    switch statement can execute the case Character
                    c statement group, then fall through the case Integer
                    i case label, where the pattern variable i would have
                not been initialized.
                  
c or i
                would have been initialized (depending on the value of
                obj).    case Character c, Integer i: ...
    case Character c, Integer i -> ...Null-Matching case Labels
Prior to this preview feature, switch expressions and
                    switch statements throw a NullPointerException
                if the value of the selector expression is null. However, pattern labels offer more
                flexibility – there are now two new null-matching case labels. First, 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.
                  
Second, a pattern label whose pattern is a total type pattern matches null if
                the value of the selector expression is null. A type pattern, T t,
                is total for a type S if the type erasure of
                    S is a subtype of the type erasure of T. For
                example, the type pattern Object obj is total for the type
                    String. Consider the following switch
                statement:
                  
        String s = ...
        switch (s) {
            case Object obj -> ... // total type pattern, so it matches null!
        }The pattern label case Object obj applies if s
                evaluates to null.
                  
If a selector expression evaluates to null and the switch block does
                not have a pattern label that is null-matching, then a
                    NullPointerException is thrown as normal.