switch

switch lets a program branch on the value of a single variable across many discrete cases, offering a more structured alternative to long else-if chains.

A switch statement compares one selector value against a series of case labels and executes the matching branch. Java's switch works with integral types, char, String, and enums as the selector, supports fall-through behavior between cases unless explicitly stopped with break, and — since more modern Java versions — also comes in a switch expression form that produces a value directly and enforces exhaustiveness.

Basic switch Statement

A switch statement evaluates its selector expression once, then jumps to the case label matching that value, executing statements from that point onward until a break (or the end of the switch) is reached.

int day = 3;
switch (day) {
    case 1:
        System.out.println("Monday");
        break;
    case 2:
        System.out.println("Tuesday");
        break;
    case 3:
        System.out.println("Wednesday");
        break;
    default:
        System.out.println("Other day");
}
// prints "Wednesday"

Case Labels and default

Each case label specifies one value the selector might match. default is optional and matches when no case label does — it can appear anywhere in the switch body (not necessarily last), though placing it last is the near-universal convention for readability.

switch (grade) {
    case 'A':
        System.out.println("Excellent");
        break;
    case 'B':
        System.out.println("Good");
        break;
    default:
        System.out.println("Unrecognized grade");
}

break and Fall-Through

Without a break, execution 'falls through' from a matched case into the following case's statements, regardless of whether that next case's label actually matches the selector value. This is a deliberate, if error-prone, feature of the classic switch statement — break is what stops it.

int n = 1;
switch (n) {
    case 1:
        System.out.println("one");
        // no break: falls through
    case 2:
        System.out.println("two");
        break;
    case 3:
        System.out.println("three");
        break;
}
// prints both "one" AND "two"

Multiple Case Labels (Intentional Fall-Through)

Fall-through is legitimately useful for grouping several values that should share the same handling — stacking case labels with no code (and no break) between them lets multiple values fall through into one shared block.

char c = 'a';
switch (c) {
    case 'a':
    case 'e':
    case 'i':
    case 'o':
    case 'u':
        System.out.println("Vowel");
        break;
    default:
        System.out.println("Consonant");
}

Switch with Integral Types

The classic switch accepts byte, short, char, int (and their wrapper types via auto-unboxing) as the selector type. Case labels must be compile-time constant expressions compatible with the selector's type.

byte level = 2;
switch (level) {
    case 1: System.out.println("Low"); break;
    case 2: System.out.println("Medium"); break;
    case 3: System.out.println("High"); break;
}

Enum switch

switch works especially well with enums: case labels use the bare constant name (not qualified with the enum type), since the selector's declared type already establishes context.

enum Direction { NORTH, SOUTH, EAST, WEST }

Direction d = Direction.EAST;
switch (d) {
    case NORTH: System.out.println("Up"); break;
    case SOUTH: System.out.println("Down"); break;
    case EAST:  System.out.println("Right"); break;
    case WEST:  System.out.println("Left"); break;
}

String switch

String is also a legal selector type. Matching is based on String content (via equals()-style comparison), and the selector must not be null — a null selector throws NullPointerException at the switch statement itself, before any case is checked.

String command = "start";
switch (command) {
    case "start":
        System.out.println("Starting...");
        break;
    case "stop":
        System.out.println("Stopping...");
        break;
    default:
        System.out.println("Unknown command");
}

Modern switch Expressions

Since Java 14, switch can also be used as an expression that directly produces a value, using arrow (->) case labels instead of colons. Arrow-form cases do not fall through by default — each case is self-contained — and yield is used to produce a value from a case that needs a block body.

Switch expressions are a Java 14+ feature (finalized after a preview period); they are distinct from switch statements even though they share the switch keyword and much of the case syntax.

int day = 3;
String name = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    default -> "Unknown";
};
System.out.println(name); // Wednesday

int score = 85;
String grade = switch (score / 10) {
    case 10, 9 -> "A";
    case 8 -> "B";
    case 7 -> {
        System.out.println("borderline");
        yield "C";
    }
    default -> "F";
};

Exhaustiveness

A switch expression must be exhaustive — the compiler requires that every possible value of the selector be covered, either through explicit cases or a default. For an enum selector whose every constant is explicitly covered by a case, a default branch can be omitted, and the compiler treats that as already exhaustive; for most other types (int, String, etc.) a default is required to guarantee exhaustiveness, since their value space isn't enumerable by the compiler.

enum Status { ACTIVE, INACTIVE }

// legal without default: every enum constant is covered
String label = switch (status) {
    case ACTIVE -> "On";
    case INACTIVE -> "Off";
};

Classic switch Statement vs. Modern switch Expression

The two forms can be mixed (a statement with colon-labels can still use break; an expression with arrow-labels does not fall through and must be exhaustive). Modern code generally prefers the expression form when a value is being produced, since it eliminates the entire class of fall-through and missing-break bugs that plague the classic form.

Classic switch statement (colon labels):
  - executes statements, does not itself produce a value
  - falls through between cases unless break is used
  - default optional, exhaustiveness not enforced

Modern switch expression (arrow labels):
  - IS an expression, evaluates to a value
  - no fall-through between cases
  - must be exhaustive (default required unless enum fully covered)
  - yield produces a value from a block-bodied case

Complete Example

public class SwitchDemo {
    enum Level { LOW, MEDIUM, HIGH }

    public static void main(String[] args) {
        // Classic switch statement with fall-through grouping
        char grade = 'B';
        switch (grade) {
            case 'A':
            case 'B':
                System.out.println("Well done");
                break;
            case 'C':
                System.out.println("Passed");
                break;
            default:
                System.out.println("See instructor");
        }

        // Modern switch expression, exhaustive over an enum, no default needed
        Level level = Level.MEDIUM;
        int threshold = switch (level) {
            case LOW -> 10;
            case MEDIUM -> 50;
            case HIGH -> 100;
        };
        System.out.println(threshold); // 50
    }
}

Quick Reference

switch (selector) { case v1: ...; break; ... default: ...; }  - classic statement
switch (selector) { case v1 -> ...; default -> ...; }         - modern expression
Allowed selector types: byte, short, char, int (+wrappers), String, enum
Fall-through: happens without break in classic (colon) form only
yield: produces a value from a block-bodied arrow case
Exhaustiveness: required for switch expressions (default, or full enum coverage)
null selector: throws NullPointerException at the switch itself

Key Takeaways

Classic switch statements fall through to the next case unless a break stops execution — this is intentional but a frequent source of bugs when forgotten.

Stacking case labels with no code between them is the standard way to group several values under one shared branch.

switch accepts byte/short/char/int (and wrappers), String, and enum selectors; a null String or wrapper selector throws NullPointerException.

Modern switch expressions (arrow syntax) produce a value directly, never fall through, and must be exhaustive.

An enum switch expression covering every constant can skip default and still be considered exhaustive; other selector types generally require one.

switch (n) { case 1: System.out.println("one"); case 2: System.out.println("two"); break; } switch (n) { case 1: System.out.println("one"); break; case 2: System.out.println("two"); break; }

A missing break causes unintended fall-through: when n is 1, both "one" and "two" print, because execution continues into case 2's statements after case 1's block.

switch (command) { // command might be null case "start":... } if (command == null) { // handle null explicitly } else { switch (command) { case "start":... } }

A null String (or wrapper) selector throws NullPointerException as soon as the switch statement is reached, before any case comparison happens. Guard against null explicitly beforehand.

int result = switch (x) { case 1 -> 10; case 2 -> 20; // no default, and x is a plain int, not an enum };

int result = switch (x) { case 1 -> 10; case 2 -> 20; default -> 0; };

A switch expression must be exhaustive. Since int's value space isn't enumerable by the compiler, a default branch is required unless every possible value is otherwise covered — omitting it is a compile error.

What happens if a case in a classic switch statement has no break at its end, and the selector matches that case?

Execution falls through into the following case's statements, running them too, regardless of whether that next case's label actually matches the selector value — this continues until a break is hit or the switch ends.

Why is stacking several case labels together with no statements between them a legitimate, intentional pattern rather than a bug?

It deliberately groups multiple selector values so they all execute the same shared block of code, by having each label fall through (with no statements to execute) into the next, until the label with the actual code is reached.

What selector types does Java's switch statement accept?

byte, short, char, and int (including their corresponding wrapper types via auto-unboxing), String, and enum types.

What happens if the selector expression of a switch statement is a null String reference?

It throws a NullPointerException as soon as the switch statement is entered, before any case comparison is attempted.

How does a modern switch expression (arrow-label form) differ from a classic switch statement in terms of fall-through behavior?

Arrow-label cases in a switch expression never fall through to the next case — each case is self-contained and only its own code runs when matched, unlike the classic colon-label form.

Why can a switch expression over an enum type omit the default branch and still compile, while one over an int typically cannot?

The compiler can verify exhaustiveness for an enum by checking that every declared constant has a corresponding case — the enum's full value space is statically known. An int's value space is effectively unbounded and not enumerable by the compiler, so a default (or some other exhaustiveness guarantee) is required to prove every possible value is handled.

What is the purpose of the yield keyword inside a switch expression's block-bodied case?

yield specifies the value that a block-bodied arrow case (one using { } instead of a single expression) should produce as the overall switch expression's result — it's the expression-form counterpart to return inside a method.

Write a switch statement that prints the number of days in a given month number (1-12), correctly grouping months with the same day count using fall-through case labels. Assume a non-leap year for February.

int month = 4; switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: System.out.println(31); break; case 4: case 6: case 9: case 11: System.out.println(30); break; case 2: System.out.println(28); break; default: System.out.println("Invalid month"); }

Grouping related month numbers under one shared case body via stacked labels avoids repeating the same println call for every month that shares a day count.

What does this code print, and why?

It prints "b" then "c". x matches case 2, which has no break, so execution falls through into case 3's statement too; case 3 does have a break, so execution stops there before reaching case 4.

Rewrite the following classic switch statement as a modern switch expression that returns the value directly: determine a shipping cost based on a String region ("domestic" -> 5, "international" -> 20, anything else -> -1 for invalid).

String region = "international"; int cost = switch (region) { case "domestic" -> 5; case "international" -> 20; default -> -1; }; System.out.println(cost); // 20

The switch expression directly evaluates to the matching case's value with no fall-through and no need for explicit break statements, and the required default branch ensures exhaustiveness since String's value space isn't enumerable by the compiler.

int x = 2;
switch (x) {
    case 1:
        System.out.println("a");
    case 2:
        System.out.println("b");
    case 3:
        System.out.println("c");
        break;
    case 4:
        System.out.println("d");
}