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 any reference type or an int type but not a
            long, float, double, or
            boolean 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.
               
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 for the instanceof Operator:
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");
        }
    }Selector Expression Type
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");
        }
    }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 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.
                  
Qualified enum Constants as case Constants
You can use qualified enum constants as
            case constants in switch expressions and
        statements.
                  
Consider the following switch expression whose selector expression is an
                enum type:
                  
    public enum Standard { SPADE, HEART, DIAMOND, CLUB }
    static void determineSuitStandardDeck(Standard d) {
        switch (d) {
            case SPADE   -> System.out.println("Spades");
            case HEART   -> System.out.println("Hearts");
            case DIAMOND -> System.out.println("Diamonds");
            default      -> System.out.println("Clubs");   
        }
    }In the following example, the type of the selector expression is an
            interface that's been implemented by two enum types. Because the type
            of the selector expression isn't an enum type, this
                switch expression uses guarded patterns instead:
                  
    sealed interface CardClassification permits Standard, Tarot {}
    public enum Standard implements CardClassification
        { SPADE, HEART, DIAMOND, CLUB }
    public enum Tarot implements CardClassification
        { SPADE, HEART, DIAMOND, CLUB, TRUMP, EXCUSE }
    static void determineSuit(CardClassification c) {
        switch (c) {
            case Standard s when s == Standard.SPADE    -> System.out.println("Spades");
            case Standard s when s == Standard.HEART    -> System.out.println("Hearts");
            case Standard s when s == Standard.DIAMOND  -> System.out.println("Diamonds");
            case Standard s                             -> System.out.println("Clubs");   
            case Tarot t when t == Tarot.SPADE          -> System.out.println("Spades or Piques");
            case Tarot t when t == Tarot.HEART          -> System.out.println("Hearts or C\u0153ur");
            case Tarot t when t == Tarot.DIAMOND        -> System.out.println("Diamonds or Carreaux");
            case Tarot t when t == Tarot.CLUB           -> System.out.println("Clubs or Trefles");
            case Tarot t when t == Tarot.TRUMP          -> System.out.println("Trumps or Atouts");
            case Tarot t                                -> System.out.println("The Fool or L'Excuse");
        }
    }However, switch expressions and statements allow qualified
                enum constants, so you could rewrite this example as follows:
                  
    static void determineSuitQualifiedNames(CardClassification c) {
        switch (c) {
            case Standard.SPADE   -> System.out.println("Spades");
            case Standard.HEART   -> System.out.println("Hearts");
            case Standard.DIAMOND -> System.out.println("Diamonds");
            case Standard.CLUB    -> System.out.println("Clubs");   
            case Tarot.SPADE      -> System.out.println("Spades or Piques");
            case Tarot.HEART      -> System.out.println("Hearts or C\u0153ur");
            case Tarot.DIAMOND    -> System.out.println("Diamonds or Carreaux");
            case Tarot.CLUB       -> System.out.println("Clubs or Trefles");
            case Tarot.TRUMP      -> System.out.println("Trumps or Atouts");
            case Tarot.EXCUSE     -> System.out.println("The Fool or L'Excuse");
        }
    }Therefore, you can use an enum constant when the type of the selector
            expression is not an enum type provided that the enum
            constant's name is qualified and its value is assignment-compatible with the type of the
            selector expression.
                  
Pattern Label Dominance
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 -> { 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);
            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 -> { break; }
        }
    }
    
    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");
        }        
    }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");
        }
    }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. This 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;
        };
    }The type of a switch expression or statement's selector
            expression can also be a generic record. As always, a switch expression
            or statement must be exhaustive. The following example doesn't compile. No match for a
                Pair exists that contains two values, both of type
                A:
                  
record Pair<T>(T x, T y) {}
class A {}
class B extends A {}
    static void notExhaustive(Pair<A> p) {
        switch (p) {                 
            // error: the switch statement does not cover all possible input values
            case Pair<A>(A a, B b) -> System.out.println("Pair<A>(A a, B b)");
            case Pair<A>(B b, A a) -> System.out.println("Pair<A>(B b, A a)");
        }        
    }The following example compiles. Interface I is sealed. Types C and D cover all possible instances:
record Pair<T>(T x, T y) {}
sealed interface I permits C, D {}
record C(String s) implements I {}
record D(String s) implements I {}
    static void exhaustiveSwitch(Pair<I> p) {
        switch (p) {
            case Pair<I>(I i, C c) -> System.out.println("C = " + c.s());
            case Pair<I>(I i, D d) -> System.out.println("D = " + d.s());
        }
    }If a switch expression or statement is exhaustive at
            compile time but not at run time, then a MatchException is
            thrown. This can happen when a class that contains an exhaustive switch
            expression or statement has been compiled, but a sealed hierarchy that is used in the
            analysis of the switch expression or statement has been subsequently
            changed and recompiled. Such changes are migration incompatible and may lead to a
                MatchException being thrown when running the
                switch statement or expression. Consequently, you need to recompile
            the class containing the switch expression or statement.
                  
Consider the following two classes ME and
                Seal:
                  
class ME {
    public static void main(String[] args) {
        System.out.println(switch (Seal.getAValue()) {
            case A a -> 1;
            case B b -> 2;
        });
    }
}sealed interface Seal permits A, B {
    static Seal getAValue() {
        return new A();
    }
}
final class A implements Seal {}
final class B implements Seal {}The switch expression in the class ME is
            exhaustive and this example compiles. When you run ME, it prints the
            value 1. However, suppose you edit Seal as follows and
            compile this class and not
                     ME:
                  
sealed interface Seal permits A, B, C {
    static Seal getAValue() {
        return new A();
    }
}
final class A implements Seal {}
final class B implements Seal {}
final class C implements Seal {}When you run ME, it throws a MatchException:
                  
Exception in thread "main" java.lang.MatchException
            at ME.main(ME.java:3)Inference of Type Arguments in Record Patterns
The compiler can infer the type arguments for a generic record pattern in
        all constructs that accept patterns: switch statements,
            instanceof expressions, and enhanced for
        statements.
                  
In the following example, the compiler infers MyPair(var s, var
                i) as MyPair<String, Integer>(String s, Integer
            i):
                  
record MyPair<T, U>(T x, U y) { }
   static void recordInference(MyPair<String, Integer> p){
        switch (p) {
            case MyPair(var s, var i) -> 
                System.out.println(s + ", #" + i);
        }
    }   See Record Patterns for more examples of inference of type arguments in record patterns.
Scope of Pattern Variable Declarations
As described in the section Pattern Matching for the instanceof Operator, 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 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.
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.