The Evolution of Switch in Java

The Evolution of Switch in Java: From Classic Statements to Powerful Pattern Matching

In this post, we’ll walk through its evolution step by step — with clear examples — so you can see how to write cleaner, safer, and more readable code today. Whether you’re on Java 8, 17, 21, or the latest versions, there’s something here for you.

Before Java 12: The Traditional Switch Statement

The classic switch was (and still is) a statement, not an expression. It couldn’t directly return a value, and you had to manage break statements carefully to avoid fall-through bugs.

public static int getNumberOfLetters(String day) {
    int numLetters;
    switch (day) {
        case "MONDAY":
        case "FRIDAY":
        case "SUNDAY":
            numLetters = 6;
            break;
        case "TUESDAY":
            numLetters = 7;
            break;
        default:
            throw new IllegalArgumentException("Invalid day: " + day);
    }
    return numLetters;
}

Pain points:

  • Verbose variable assignments in every case.
  • Easy to forget break → accidental fall-through.
  • Not very expressive for complex logic.

You could return directly from cases, but that exits the whole method, which isn’t always ideal.

Java 12 & 13: Switch Expressions (Preview → Refined)

Java 12 introduced switch expressions as a preview feature. You could now use switch as an expression that returns a value, with the new arrow syntax -> (no fall-through by default). Multiple constants per case with commas.

Java 12 example:

int numLetters = switch (day) {
    case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
    case "TUESDAY" -> 7;
    default -> throw new IllegalArgumentException("Invalid day: " + day);
};

Java 13 added the yield keyword for blocks with multiple statements:

String result = switch (day) {
    case "MONDAY", "FRIDAY", "SUNDAY" -> "Weekend vibe";
    case "TUESDAY" -> {
        System.out.println("Processing Tuesday...");
        yield "Mid-week";
    }
    default -> "Other day";
};

This made switch much more concise and less error-prone. Switch expressions became permanent in Java 14.

Java 17+: Pattern Matching for Switch (Preview → Standard in Java 21)

This is where things got really exciting. Pattern matching lets you switch on types, deconstruct objects, and add guards — no more endless instanceof chains.

Basic Type Patterns

public static String describe(Object obj) {
    return switch (obj) {
        case String s -> "String of length " + s.length();
        case Integer i -> "Integer: " + i;
        case null -> "Null value";
        default -> "Unknown: " + obj.getClass().getSimpleName();
    };
}
  • Automatic type casting into the pattern variable.
  • null can (and should) be handled explicitly as a case.
  • Exhaustiveness checking (especially powerful with sealed types).

Guarded Patterns with when (stabilized later)

String checkNumber(Object obj) {
    return switch (obj) {
        case Integer i when i > 0 -> "Positive: " + i;
        case Integer i when i < 0 -> "Negative: " + i;
        case Integer i -> "Zero";
        case String s when s.isBlank() -> "Blank string";
        default -> "Other";
    };
}

Record Patterns (Java 19+ preview, standard in 21)

Deconstruct records directly in the case label:

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

double getArea(Object shape) {
    return switch (shape) {
        case Circle(Point center, int r) when r > 0 -> Math.PI * r * r;
        case Rectangle(int length, int width) -> length * width;
        default -> 0.0;
    };
}

Nested record patterns (enhanced in Java 20):

record Line(Point p1, Point p2) {}

void describeLine(Object obj) {
    switch (obj) {
        case Line(Point(int x1, int y1), Point(int x2, int y2)) 
            -> System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
        default -> System.out.println("Not a line");
    }
}

Recent Enhancements (Java 23+)

Primitive type patterns in switch and instanceof are available as preview features (introduced around Java 23 and continuing in later previews). This further unifies the language.

Example (preview):

// With primitive patterns (preview)
String classifyNumber(int num) {
    return switch (num) {
        case int i when i < 0 -> "Negative";
        case 0 -> "Zero";
        case int i when i > 100 -> "Large positive";
        default -> "Positive";
    };
}

Summary Table: Evolution of Switch Across Java Versions

Java Version Key Feature Status Highlights
Pre-12 Traditional switch statement Standard break, fall-through, constants only (int, enum since 5, String since 7)
12 Switch Expressions + Arrow -> Preview Returns value, multiple cases with commas, no fall-through
13 yield for blocks Preview Multi-statement cases in expressions
14 Switch Expressions Standard Production-ready
17 Pattern Matching (Type Patterns) Preview case Type var, null cases, guards (when)
19-20 Record Patterns + Nested Preview Deconstruct records, nested patterns
21 Pattern Matching for Switch Standard Full power: types, records, guards, exhaustiveness with sealed classes
23+ Primitive Type Patterns Preview (ongoing) Switch & instanceof on primitives directly

Post a Comment

Previous Post Next Post