Command Palette

Search for a command to run...

PHASE 2Beginner ~30 min· topic 3 of 10

Topic 2.3

The switch Statement

In one line

A switch statement compares one value against a list of constant case labels and jumps straight to the matching one. In the classic colon form, execution falls through into the following cases until a break, which is both its trick and its most common bug.

Think of it like this

A lift (elevator) panel. You press one button, say 3, and the lift goes straight to floor 3. It doesn't stop at floors 1 and 2 to check. A switch works the same way: it takes one value and jumps directly to the matching case, instead of asking a chain of yes/no questions like an else-if ladder.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Selector
The value in switch (...) that is compared against the cases.
Case label
A case 3: line that marks where to jump when the selector equals 3.
Compile-time constant
A value the compiler knows while compiling, such as 7, "red", or a static final int set to a literal.
Fall-through
In a colon-style switch, carrying on into the next case's statements because there was no break.
break
A statement that jumps out of the switch (or loop) immediately.
default
The label that runs when no case matches.
enum
A type with a fixed list of named values, like enum Day { MON, TUE, WED }. Phase 6 covers them fully.
Jump table
A list of addresses indexed by the case value, so the program can jump to the right case in one step.

Step by step

01The shape of a switch

Write switch, the selector in round brackets, then a block of case labels. Each label is followed by a colon and the statements to run. break leaves the switch.

With day = 3, Java jumps straight to case 3:, prints Wednesday, hits break, and continues after the closing brace.

Main.javawhole filejava
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("Some other day");
}
System.out.println("done");
terminal
$ java Main.java
── expected output ──
Wednesday
done

02Fall-through: what happens without break

A case label is only an entry point, not a wall. Once Java enters at a label, it runs downward until a break (or the end). Remove the breaks from the example above with day = 2, and you get Tuesday, Wednesday and 'Some other day'.

This design comes from C. It's why break after every case is a habit you must build, and why Java 14 added arrow labels that never fall through (Topic 2.4).

Fall-through: what happens without breakdiagram
Rendering diagram…

03Fall-through on purpose: grouping cases

Fall-through is useful when several values share one action. Stack the labels with nothing between them: entering at any of them flows down to the shared code.

Since Java 14 you can also write several values in one label, case 6, 7:, even in the colon form. It's clearer than stacking.

Main.javawhole filejava
int day = 6;
switch (day) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        System.out.println("Weekday");
        break;
    case 6, 7:                      // Java 14+: several labels in one case
        System.out.println("Weekend");
        break;
    default:
        System.out.println("Not a day");
}
terminal
$ java Main.java
── expected output ──
Weekend

04What can be switched on, and what labels can be

Selector types (classic switch): char, byte, short, int, their wrappers, String (since Java 7), and enums (since Java 5). Switching on a long gives error: selector type long is not allowed in recent JDKs (older ones said incompatible types: possible lossy conversion from long to int).

Labels must be constant expressions and unique. A final int limit = 3; local is a constant and works; a plain int limit = 3; is not and fails with constant expression required. Two case 1: labels fail with duplicate case label.

For an enum selector, labels are the bare constant names: case RED:, not case Color.RED: (qualified names are allowed only from Java 21).

Main.javawhole filejava
enum Light { RED, YELLOW, GREEN }

Light light = Light.YELLOW;
switch (light) {
    case RED:    System.out.println("Stop");  break;
    case YELLOW: System.out.println("Slow");  break;
    case GREEN:  System.out.println("Go");    break;
}

String command = "stop";
switch (command) {                 // String switch: Java 7+
    case "start": System.out.println("Starting"); break;
    case "stop":  System.out.println("Stopping"); break;
    default:      System.out.println("Unknown command");
}
terminal
$ java Main.java
── expected output ──
Slow
Stopping

05Scope inside a switch block

The whole switch body is one block. A variable declared under case 1: is visible (but not initialised) under case 2:, and declaring the same name again is variable x is already defined.

Give a case its own braces when it needs local variables: case 1: { int x = 10; ... break; }. The arrow form in Topic 2.4 gives every case its own scope automatically.

Main.javawhole filejava
switch (op) {
    case 1: {
        int result = a + b;      // scoped to these braces
        System.out.println(result);
        break;
    }
    case 2: {
        int result = a - b;      // a different variable, no clash
        System.out.println(result);
        break;
    }
}

06null selectors throw

A classic switch on a String, enum or wrapper first needs the actual value, so it calls a method on the selector (hashCode() for a String, ordinal() for an enum, intValue() for a wrapper). If the selector is null, that call throws NullPointerException before any case is considered, and default doesn't catch it.

Guard with if (command == null) first, or use Java 21's case null in a pattern switch.

terminal
$ javac Main.java && java Main
── expected output ──
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "<local2>" is null
at Main.main(Main.java:4)
With `String command = null;` declared right after `args`. It says `<local2>`, not `<local1>`, because javac copies the selector into a hidden temporary variable before switching on it.

07How the JVM jumps: tableswitch and lookupswitch

For dense int cases (1, 2, 3), javac emits a tableswitch: an array of jump targets indexed by value - low. Finding the right case is one subtraction, one bounds check and one jump, whatever the number of cases.

For sparse values (1, 100, 5000) a table would be mostly empty, so javac emits a lookupswitch: a sorted list of (value, target) pairs that the JVM can binary-search. Either way it's not a chain of comparisons like an else-if ladder. A String switch becomes a lookupswitch on hashCode(), followed by equals() to rule out hash collisions, then a second, dense switch on the matched index.

terminal
$ javap -c Main
── expected output ──
static java.lang.String day(int);
Code:
0: iload_0
1: tableswitch { // 1 to 3
1: 28
2: 31
3: 34
default: 37
}
...
static java.lang.String sparse(int);
Code:
0: iload_0
1: lookupswitch { // 3
1: 36
100: 39
5000: 42
default: 45
}

Try it yourself

  1. 1

    Watch fall-through happen

    Run 'The missing-break bug'. Then change day to 1 and predict the output before running (all four lines). Change it to 9: only 'Some other day' prints.

  2. 2

    Add a case with a shared action

    In 'Switching on String, char and enum', make case "halt" behave exactly like "stop" by stacking the labels, then rewrite it as case "stop", "halt":. Both compile on Java 14 and later.

  3. 3

    Trigger the null case

    Set String command = null; and run. Read the exception: it names String.hashCode(), which proves the switch calls hashCode() first. Add if (command == null) { System.out.println("No command"); } else { ... } around the switch to fix it.

Code & diagrams

Day names with break New tab
Sign in to run this example in your browser.

Expected output

1 -> Monday
3 -> Wednesday
9 -> unknown
The missing-break bug New tab
Sign in to run this example in your browser.

Expected output

Switch on 2 without break:
  Tuesday
  Wednesday
  Some other day
Useful fall-through: days left in the week New tab

Each case adds one and deliberately falls into the next. Entering at Wednesday (3) adds Wednesday, Thursday and Friday.

Sign in to run this example in your browser.

Expected output

Monday: 5
Wednesday: 3
Friday: 1
Sunday: 0
Switching on String, char and enum New tab
Sign in to run this example in your browser.

Expected output

Stopping
Well done
Slow down
What a String switch compiles to (roughly)java

A hand-written equivalent of the code javac generates. You never write this yourself; it explains why a null String throws and why the cost is one hash plus one equals.

// switch (command) { case "start": A; break; case "stop": B; break; default: C; }
int index = -1;
switch (command.hashCode()) {          // NullPointerException if command is null
    case 109757538:                    // "start".hashCode()
        if (command.equals("start")) index = 0;
        break;
    case 3540994:                      // "stop".hashCode()
        if (command.equals("stop")) index = 1;
        break;
}
switch (index) {
    case 0: A; break;
    case 1: B; break;
    default: C;
}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use a variable as a case label

Declare int limit = 3; and write case limit:.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        int x = 5;
        int limit = 3;
        switch (x) {
            case limit: System.out.println("a");
        }
    }
}
terminal
$ javac Main.java
── what you'll see ──
Main.java:6: error: constant expression required
case limit: System.out.println("a");
^
1 error

Break #2

Repeat a case label

Write case 1: twice in the same switch.

terminal
$ javac Main.java
── what you'll see ──
Main.java:7: error: duplicate case label
case 1:
^
1 error

Break #3

Switch on a long

Declare long id = 2; and write switch (id).

terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: selector type long is not allowed
switch (id) {
^
1 error

Myth vs fact

Myth

default must be the last label.

Fact

It can go anywhere, and fall-through applies to it like any other label. Putting it last is a readability convention.

Myth

A switch checks the cases one by one, like an else-if ladder.

Fact

For ints it compiles to a tableswitch (one indexed jump) or lookupswitch (a search of sorted keys).

Myth

default catches a null selector.

Fact

A classic switch on null throws NullPointerException before any label is considered.

Myth

Switch on String compares with ==.

Fact

It uses hashCode() and then equals(), so it compares contents, like equals does.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    javac chooses between tableswitch and lookupswitch with a cost estimate: roughly, a table is used when its size isn't much bigger than the number of cases. The JIT may further compile either into a jump table, a binary search, or a few compares, depending on the profile.

  • ▸

    A switch on an enum from another class compiles to a lookup in a synthetic array ($SwitchMap$...) mapping ordinal() to a case index, so reordering enum constants in a library doesn't break already-compiled switches. Since Java 21, pattern-style enum switches may instead use invokedynamic with SwitchBootstraps.

  • ▸

    javac -Xlint:fallthrough warns on every case that falls into the next with code in between. Many teams turn it on and mark intended fall-through with a // fall through comment (tools like Error Prone and Checkstyle recognise it).

  • ▸

    Constant folding makes static final fields usable as labels, and it also means their values are inlined into the calling class at compile time. Change the constant in a library without recompiling the caller and the old value stays baked into its switch.

Remember this

  1. 1

    The value being tested is the selector. In a classic switch it may be a char, byte, short, int, their wrappers (Character, Byte, Short, Integer), an enum (Java 5), or a String (Java 7). long, float, double and boolean are not allowed (until pattern matching in Java 21, Topic 2.4 shows that).

  2. 2

    Each case label must be a compile-time constant: a literal like 3 or "stop", an enum constant, or a static final variable initialised with a constant. Labels must be unique. A normal variable as a label is a compile error (constant expression required).

  3. 3

    In the colon form (case 1:), once Java jumps to a case it keeps running every statement below it, through the following cases, until it reaches a break or the end of the switch. This is fall-through. Forgetting break is the classic switch bug; using fall-through on purpose lets several cases share code.

  4. 4

    default: runs when no case matches. It's optional in a switch statement, and if it's missing and nothing matches, nothing happens. It may appear anywhere, but put it last so readers find it.

  5. 5

    A null selector (a String, enum or wrapper that is null) throws NullPointerException before any case is checked. A classic switch has no way to match null; Java 21 adds case null (Topic 2.4).

  6. 6

    The compiler turns a switch on ints into a jump table (tableswitch) when the case values are close together, or a sorted lookup (lookupswitch) when they're spread out. A switch on String is compiled into a switch on hashCode(), then an equals() check.

Explain it without notes

01

What is fall-through in a switch statement, and when is it useful?

02

Which types can a classic switch statement select on, and why can't it use long?

03

What happens when a switch's String selector is null?

04

How does the JVM execute a switch on int, and how does a String switch work?

Practice

01

Write a switch that converts a month number (1–12) to the number of days in it, for a non-leap year, grouping months that share a length.

02

Write a calculator: given char op and two ints, use a switch to print the result for +, -, * and /, and Unknown operator otherwise. Test with '*', '/' and '%' on 12 and 4.

03

Using an enum Size { SMALL, MEDIUM, LARGE } and a switch, print the price of a coffee: 2, 3 and 4 respectively, for all three sizes.

Trade-offs

  • ↔

    A switch is clearer than an else-if ladder for one value against many constants, but it can't test ranges or combine conditions (until pattern guards in Java 21).

  • ↔

    Intentional fall-through is compact but easy to misread and easy to break when someone adds a case. Prefer comma-separated labels or the arrow form when you can.

  • ↔

    The classic switch statement is still everywhere in existing code, so you must read it fluently, but for new code that computes a value, the switch expression (Topic 2.4) is safer.

Done when you can

  • Done when you can write a classic switch with break and default from memory.

  • Done when you can predict the output of a switch with missing breaks.

  • Done when you can list the allowed selector types and explain why case labels must be constants.

  • Done when you can explain why a null String selector throws, and guard against it.

  • Done when you can describe tableswitch vs lookupswitch and how a String switch is compiled.