Skip to content
Java fundamentals 3 min read

Control Statements

Control statements decide which blocks of code run and in what order. Java provides conditional branching through if/else and switch. Modern Java has dramatically improved switch with expressions, arrow labels, and pattern matching, making branching both safer and more concise.

The if Statement

An if runs its block when a boolean condition is true. An optional else handles the false case.

int temperature = 30;

if (temperature > 25) {
    System.out.println("It's warm");
} else {
    System.out.println("It's cool");
}

Output:

It's warm

The condition must be a boolean — Java does not treat 0 or null as false, which prevents a whole class of bugs.

Tip: Always use braces, even for single-statement bodies. Braceless if statements are a frequent source of bugs when a second line is added later.

The else-if Ladder

Chain conditions to select one branch among many. The first matching condition wins; the rest are skipped.

int score = 82;
char grade;

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 70) {
    grade = 'C';
} else {
    grade = 'F';
}
System.out.println(grade);   // B

The Classic switch Statement

A traditional switch compares a value against constant labels. Each case falls through to the next unless you break.

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

Warning: Forgetting break causes fall-through: execution continues into the next case. This is a classic bug. Modern arrow-style switch eliminates it.

switch supports int-compatible types, String, enums, and (since Java 21) patterns.

Modern Switch Expressions

Java 14+ introduced switch expressions with arrow labels (->). They return a value, never fall through, and require the cases to be exhaustive. Multiple labels can share one arm.

int day = 3;
String type = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7          -> "Weekend";
    default            -> "Invalid";
};
System.out.println(type);    // Weekday

Output:

Weekday

When an arm needs multiple statements, wrap it in a block and produce the result with yield.

int quarter = 2;
String label = switch (quarter) {
    case 1 -> "Q1";
    case 2 -> {
        String season = "Spring";
        yield "Q2 (" + season + ")";
    }
    default -> "?";
};
System.out.println(label);   // Q2 (Spring)

Because switch expressions must be exhaustive, the compiler catches missing cases — especially valuable with enums, where you can often omit default entirely.

Pattern Matching in Switch

Java 21 finalized pattern matching for switch, letting you branch on type and bind a variable simultaneously.

Object obj = 42;
String desc = switch (obj) {
    case Integer i when i > 10 -> "big int: " + i;
    case Integer i            -> "small int: " + i;
    case String s             -> "string: " + s;
    default                   -> "other";
};
System.out.println(desc);    // big int: 42

The when clause adds a guard condition. Cases are evaluated top to bottom, so order more specific patterns first.

Best Practices

  • Prefer arrow-style switch expressions over classic statements to avoid fall-through bugs.
  • Keep if/else ladders shallow; deep nesting is a sign to extract methods or use early returns.
  • Use guard clauses (if (invalid) return;) to reduce nesting and flatten logic.
  • Make switch exhaustive and lean on the compiler to catch unhandled enum constants.
  • Always brace conditional bodies, even one-liners.
Last updated June 1, 2026
Was this helpful?