00:00

switch Statement in Java – From Classic switch to Pattern Matching

The switch statement chooses one of many code paths based on a single value. Instead of a long if-else-if chain comparing the same variable again and again, you list the possible values as case labels and Java jumps straight to the matching one.

Switch has changed a lot in recent Java versions. This tutorial starts with the classic form and break, then covers switch expressions with arrows and yield (Java 14), and finally pattern matching for switch (Java 21).

1. The Classic switch Statement

Flow of a switch statement in Java
Syntax
switch (expression) {
    case value1:
        // statements
        break;
    case value2:
        // statements
        break;
    default:
        // runs when no case matches
}
ClassicSwitch.java
public class ClassicSwitch {
    public static void main(String[] args) {
        int day = 3;
        String name;
        switch (day) {
            case 1:
                name = "Monday";
                break;
            case 2:
                name = "Tuesday";
                break;
            case 3:
                name = "Wednesday";
                break;
            default:
                name = "Another day";
        }
        System.out.println("Day " + day + " is " + name);
    }
}
Output
Day 3 is Wednesday
  • The expression can be byte, short, char, int, their wrapper classes, a String (Java 7+) or an enum. Since Java 21, any reference type can be used with patterns.
  • Case labels must be constants of a compatible type, and no two labels may be equal.
  • default is optional and can appear anywhere, though it is usually placed last.
  • break ends the switch.

2. Fall-Through and Grouped Cases

In the classic form, when a case matches, Java runs its statements and then keeps going into the following cases until it reaches a break or the end. This is called fall-through. It is occasionally useful, but forgetting a break is a common bug.

FallThrough.java
public class FallThrough {
    public static void main(String[] args) {
        // Without break, execution "falls through" to the next case
        int level = 2;
        System.out.println("Unlocked features for level " + level + ":");
        switch (level) {
            case 3:
                System.out.println("  - analytics");
            case 2:
                System.out.println("  - export");
            case 1:
                System.out.println("  - basic editing");
        }

        // Grouping several labels on one case
        char grade = 'B';
        switch (grade) {
            case 'A': case 'B':
                System.out.println("Grade " + grade + ": excellent");
                break;
            case 'C':
                System.out.println("Grade " + grade + ": good");
                break;
            default:
                System.out.println("Grade " + grade + ": needs improvement");
        }
    }
}
Output
Unlocked features for level 2:
  - export
  - basic editing
Grade B: excellent

Level 2 printed its own feature and then fell through to level 1's feature, which is intended here. The grade example shows how to give several labels the same code.

3. Switch Expressions (Java 14+)

Modern Java offers arrow labels (case X ->) and lets a switch produce a value. Arrow cases never fall through, so no break is needed, and several constants can share one case with commas.

SwitchExpression.java
public class SwitchExpression {
    enum Size { SMALL, MEDIUM, LARGE, XL }

    public static void main(String[] args) {
        // Arrow labels: no fall-through, no break needed (Java 14+)
        String month = "FEB";
        int days = switch (month) {
            case "APR", "JUN", "SEP", "NOV" -> 30;
            case "FEB" -> 28;
            default -> 31;
        };
        System.out.println(month + " has " + days + " days");

        // With enums, the compiler checks that every constant is handled
        Size size = Size.LARGE;
        double price = switch (size) {
            case SMALL -> 199;
            case MEDIUM -> 249;
            case LARGE, XL -> {
                double base = 299;
                yield size == Size.XL ? base + 50 : base;   // yield returns a value from a block
            }
        };
        System.out.println(size + " costs " + price);
    }
}
Output
FEB has 28 days
LARGE costs 299.0
  • A switch expression must be exhaustive: every possible value must be handled, usually with default. For enums, listing every constant is enough, and the compiler will tell you if you miss one.
  • When a case needs several statements, use a block { } and return its value with yield.
  • Arrow labels can also be used in switch statements that produce no value.

4. Pattern Matching for switch (Java 21)

Since Java 21, a switch can match on types and record patterns, add when guards, and even handle null explicitly:

PatternSwitch.java
public class PatternSwitch {
    sealed interface Shape permits Circle, Rectangle, Triangle { }
    record Circle(double radius) implements Shape { }
    record Rectangle(double width, double height) implements Shape { }
    record Triangle(double base, double height) implements Shape { }

    // Java 21: switch on types, with record patterns and guards
    static String describe(Object obj) {
        return switch (obj) {
            case null -> "nothing";
            case Integer i when i > 100 -> "a big number " + i;
            case Integer i -> "a number " + i;
            case String s -> "text of length " + s.length();
            case Circle(double r) -> String.format("a circle with area %.2f", Math.PI * r * r);
            case Rectangle(double w, double h) -> "a rectangle with area " + (w * h);
            default -> "something else: " + obj.getClass().getSimpleName();
        };
    }

    public static void main(String[] args) {
        Object[] things = {7, 250, "hello", new Circle(1), new Rectangle(2, 3), new Triangle(3, 4), null};
        for (Object t : things) {
            System.out.println(describe(t));
        }
    }
}
Output
a number 7
a big number 250
text of length 5
a circle with area 3.14
a rectangle with area 6.0
something else: Triangle
nothing
  • case Integer i matches any Integer and binds it to i.
  • case Integer i when i > 100 adds a guard condition. More specific cases must come before more general ones.
  • case Circle(double r) deconstructs a record into its components.
  • case null handles a null value; without it, switching on null throws NullPointerException.
  • With a sealed interface, a switch that covers every permitted subtype needs no default.

5. Classic vs Arrow vs Pattern Switch

FeatureClassic (case X:)Arrow (case X ->)Pattern (Java 21)
Fall-throughYes, unless breakNoNo
Can return a valueOnly as an expression with yieldYesYes
Multiple labelscase 1: case 2:case 1, 2 ->Type patterns
Matches onConstantsConstantsTypes, records, guards, null
Exhaustiveness checkedNo (statements)Yes (expressions)Yes

6. switch vs if-else

  • Use switch when one value is compared against many constants, enum values or types.
  • Use if-else for ranges (marks >= 85), unrelated conditions or combinations with &&/|| (although guards make switch more flexible too).
  • For large constant mappings, a Map lookup may be simplest of all.

7. Common Mistakes

  • Forgetting break in a classic switch and falling through by accident.
  • Switching on a null String or wrapper without case null: NullPointerException.
  • Using variables or ranges as case labels; labels must be constants (use guards or if-else for ranges).
  • Putting a general pattern before a specific one: the compiler reports the later case as dominated.
  • Switching on long, float, double or boolean values with constant labels, which is not allowed.

8. Summary

  • The classic switch jumps to a matching case and needs break to avoid fall-through.
  • Switch works with integers, char, strings, enums and wrappers, and with any type when using patterns.
  • Arrow labels and switch expressions (Java 14) remove fall-through and return values with yield.
  • Pattern matching for switch (Java 21) matches types and records, supports guards and case null.
  • Prefer modern arrow switches in new code.

Related Tutorials

Reference: dev.java: Branching with Switch Expressions