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
ifstatements 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
breakcauses fall-through: execution continues into the next case. This is a classic bug. Modern arrow-styleswitcheliminates 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
switchexpressions over classic statements to avoid fall-through bugs. - Keep
if/elseladders 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
switchexhaustive and lean on the compiler to catch unhandled enum constants. - Always brace conditional bodies, even one-liners.