Pattern Matching for Switch in Java

Naveen AnamaneniUpdated · 9 min read

Pattern matching for switch lets a switch choose a case based on the type of a value, not just on a fixed constant. Each case can hold a pattern such as case String s, which tests whether the value is a String and, if so, gives you a variable s of that type. It became a standard feature in Java 21 (JEP 441). It replaces long chains of if (o instanceof ...) else if (o instanceof ...) with one clear, checked switch. In this lesson, you will learn the syntax, the rules for ordering cases, why the compiler insists on covering every value, and how the JVM runs it.

Before and After

Without pattern matching, testing several types means an instanceof chain:

Chain.java
static String describe(Object o) {
    if (o instanceof Integer i) {
        return "an Integer: " + (i * 2);
    } else if (o instanceof String s) {
        return "a String of length " + s.length();
    } else {
        return "something else";
    }
}

With pattern matching for switch, the same logic is one expression:

Switch.java
static String describe(Object o) {
    return switch (o) {
        case Integer i -> "an Integer: " + (i * 2);
        case String s -> "a String of length " + s.length();
        default -> "something else";
    };
}

The switch version is shorter, and, more importantly, the compiler checks it: it rejects cases that can never be reached and switches that miss some values.

Syntax

Syntax
switch (selector) {
    case Type variable -> body;
    case Type variable when condition -> body;
    case null -> body;
    default -> body;
}
PartMeaning
selectorAny reference type: Object, an interface, a class, an enum, a record, a sealed type
case Type variableA type pattern: matches if the selector is an instance of Type, and binds variable
when conditionA guard: an extra condition (see "Guards in Switch (When Clause)")
case nullHandles null (see "Handling Null in Switch")
defaultMatches anything not matched earlier

Example: Describing Any Object

Let's see an example that handles several unrelated types, including an array and a generic List:

Describe.java▶ Try it
import java.time.LocalDate;
import java.util.List;
 
public class Describe {
    static String describe(Object o) {
        return switch (o) {
            case Integer i -> "an Integer: " + (i * 2);
            case String s -> "a String of length " + s.length();
            case int[] arr -> "an int array of size " + arr.length;
            case List<?> list -> "a List with " + list.size() + " items";
            case LocalDate d -> "a date in " + d.getYear();
            default -> "something else: " + o.getClass().getSimpleName();
        };
    }
 
    public static void main(String[] args) {
        System.out.println(describe(21));
        System.out.println(describe("Navika"));
        System.out.println(describe(new int[] {1, 2, 3}));
        System.out.println(describe(List.of("a", "b")));
        System.out.println(describe(LocalDate.of(2026, 11, 13)));
        System.out.println(describe(3.5));
    }
}
Output
an Integer: 42
a String of length 6
an int array of size 3
a List with 2 items
a date in 2026
something else: Double

Explanation: The switch tests the cases from top to bottom and runs the first one that matches. In each case, the pattern variable already has the right type, so i * 2, s.length() and arr.length need no cast. 3.5 is a Double, which no case matches, so default runs. Generic types are written with a wildcard, List<?>, because the element type isn't known at run time.

Top to bottom, and the first match wins

Case Order and Dominance

Because the first match wins, a more general case placed before a more specific one would hide it. The compiler rejects this; it says the later case is dominated:

E2.java
return switch (o) {
    case CharSequence cs -> "text";
    case String s -> "string";      // every String is a CharSequence
    default -> "other";
};
Compiler output
E2.java:5: error: this case label is dominated by a preceding case label
            case String s -> "string";
                 ^
1 error

The same check applies to constants: a case 5 after case Integer n can never run, and is rejected with the same message. The fix is to put specific cases first, general ones last.

Every Value Must Be Covered

A pattern switch must be exhaustive: every possible value of the selector must match some case. For a selector of type Object, that normally means adding default. Without it:

Compiler output
E3.java:3: error: the switch expression does not cover all possible input values
        return switch (o) {
               ^
1 error

This rule applies to pattern switch statements too, not only expressions:

Compiler output
S1.java:3: error: the switch statement does not cover all possible input values
        switch (o) {
        ^
1 error

Old-style switch statements over int, String or enums without patterns don't need to be exhaustive, for backward compatibility. With sealed types, you can cover every case without default; see "Exhaustive Switch with Sealed Types".

Mixing Enum Constants and Patterns

Since Java 21, a case label can name an enum constant with its type, such as Coin.ONE. This lets one switch handle enum constants and record types that implement the same sealed interface:

Money.java▶ Try it
public class Money {
    sealed interface Currency permits Coin, Note { }
    enum Coin implements Currency { ONE, TWO, FIVE }
    record Note(int value) implements Currency { }
 
    static String value(Currency c) {
        return switch (c) {
            case Coin.ONE -> "1 rupee coin";
            case Coin.TWO, Coin.FIVE -> "bigger coin";
            case Note n -> n.value() + " rupee note";
        };
    }
 
    public static void main(String[] args) {
        System.out.println(value(Coin.ONE));
        System.out.println(value(Coin.FIVE));
        System.out.println(value(new Note(500)));
    }
}
Output
1 rupee coin
bigger coin
500 rupee note

Explanation: The selector type is the sealed interface Currency. The three Coin constants and the Note record cover every possible value, so no default is needed.

No Fall-Through into Patterns

With the old colon (:) style, a case without break falls through to the next one. That isn't allowed into a case that declares a pattern variable, because the variable would never have been set:

Compiler output
E4.java:6: error: illegal fall-through to a pattern
            case Integer i:
                 ^
  (the previous case label is missing a break)
1 error

Use the arrow (->) form with patterns; it never falls through.

Under the Hood: How a Pattern Switch Compiles

A constant switch compiles to a jump table. A pattern switch can't, because the JVM must test types. Instead, javac emits an invokedynamic call to a method named typeSwitch in java.lang.runtime.SwitchBootstraps. Here is the real javap output for this method:

T.java
static String d(Object o) {
    return switch (o) {
        case Integer i when i > 10 -> "big";
        case Integer i -> "int";
        case String s -> "str";
        default -> "other";
    };
}
javap -c -v T (trimmed)
   2: invokestatic  #7    // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
  11: invokedynamic #13,  0   // InvokeDynamic #0:typeSwitch:(Ljava/lang/Object;I)I
  16: tableswitch   { // 0 to 2
                 0: 44
                 1: 68
                 2: 79
           default: 90
      }
  44: aload_1
  45: checkcast     #17   // class java/lang/Integer
  ...
BootstrapMethods:
  0: #63 REF_invokeStatic java/lang/runtime/SwitchBootstraps.typeSwitch:(...)
    Method arguments:
      #17 java/lang/Integer
      #17 java/lang/Integer
      #27 java/lang/String

Explanation:

  1. Objects.requireNonNull: with no case null, a null selector throws NullPointerException right away.
  2. typeSwitch: given the object and a starting index, it returns the number of the first case whose type matches: 0, 1 or 2 (the case labels Integer, Integer, String listed under "Method arguments"), or 3 for none.
  3. tableswitch: an ordinary jump table on that number.
  4. The guard: if i > 10 is false, the code calls typeSwitch again with the next index, so matching continues from the following case.

The bootstrap method runs once per call site; the JVM then optimizes the generated code, so pattern switches are fast.

Common Mistakes

Putting a general case before a specific one. The compiler reports "dominated"; reorder the cases.

Forgetting default when switching on Object. The switch must cover every value.

Expecting a pattern switch to handle null. Without case null, it throws NullPointerException.

Writing List<String> in a case. Generic type arguments are erased at run time; use List<?>.

Interview Corner

What is pattern matching for switch?

Since Java 21, case labels can contain patterns such as case String s, which test the selector's type and bind a variable of that type. Cases are tested top to bottom; the first match wins. The compiler rejects dominated cases (a case that can never be reached because an earlier one covers it) and requires pattern switches to be exhaustive, usually with default or a sealed type. It also supports guards (when), case null, record patterns, and qualified enum constants.

How does a pattern switch work at the bytecode level?

javac compiles it to an invokedynamic call bootstrapped by SwitchBootstraps.typeSwitch, which receives the case types as static arguments. At run time it returns the index of the first matching case, and a tableswitch jumps to that case's code. When a guard fails, the code calls typeSwitch again starting from the next index. Without case null, a null selector is rejected first with Objects.requireNonNull.

Practice

  1. Easy: Rewrite an if/else if chain that tests Integer, Long and Double into a pattern switch that returns each value doubled as a String.
  2. Harder: Compile T.java above and run javap -c -v T. Find the typeSwitch call and the list of types it receives, then add a case Long l and see how the list changes.

Quick check: Pattern matching for switch

4 questions
1In which order are case patterns tested?
2Why is case String s after case CharSequence cs an error?
3A switch on Object has cases for String and Integer only. What happens?
4Which bootstrap method do pattern switches use?

Key Takeaways

Was this tutorial helpful?

Lesson 456 of 482