Java Control Statements
Control statements determine the order in which your program executes code. In Java, they fall into three big groups:
- Decision-making (selection): if, if-else, else if, switch
- Looping (iteration): for, enhanced for-each, while, do-while
- Jump (branching): break, continue, return, plus labeled break/continue
This guide explains each construct, when to use it, and common pitfalls—using Java 8+ syntax and the modern switch expression (Java 14+).
1) Decision-Making Statements
1.1 if, if-else, and else if
Use if when you want to run code only if a condition is true. Chain with else if for multiple branches, and finish with else for the “default” path.
int score = 86;
if (score >= 90) {
System.out.println(“Grade: A”);
} else if (score >= 80) {
System.out.println(“Grade: B”); // prints
} else if (score >= 70) {
System.out.println(“Grade: C”);
} else {
System.out.println(“Grade: D/F”);
}
Tips
Conditions must be boolean.
Prefer short-circuit operators && and || to avoid unnecessary evaluations (and potential NullPointerExceptions).
String s = null;
if (s != null && s.length() > 5) { // safe due to short-circuiting
System.out.println(“Long”);
}
1.2 Nested if (use sparingly)
int age = 20;
boolean hasID = true;
if (age >= 18) {
if (hasID) {
System.out.println(“Entry allowed”);
} else {
System.out.println(“ID required”);
}
} else {
System.out.println(“Underage”);
}
Best practice: Extract logic into methods or use guard clauses to reduce nesting.
1.3 switch (Statements and Expressions)
Classic switch statement
int day = 3;
switch (day) {
case 1: System.out.println(“Mon”); break;
case 2: System.out.println(“Tue”); break;
case 3: System.out.println(“Wed”); break; // prints
default: System.out.println(“Other”);
}
Works with: byte, short, char, int, enum, String (since Java 7).
Pitfall: Forgetting break causes fall-through.
Modern switch expression (Java 14+)
- Arrow labels, no fall-through
- Returns a value
- yield for multi-statement branches
int month = 2;
int days = switch (month) {
case 1,3,5,7,8,10,12 -> 31;
case 4,6,9,11 -> 30;
case 2 -> 28; // ignore leap year for brevity
default -> throw new IllegalArgumentException(“Invalid month”);
};
System.out.println(days);
With logic + yield:
String role = “admin”;
int accessLevel = switch (role) {
case “guest” -> 1;
case “user” -> 2;
case “admin” -> {
logAudit(“Admin login”); // some side-effect
yield 3; // return value from this arm
}
default -> 0;
};
Pattern matching for switch (newer JDKs): Recent JDKs add pattern matching (instanceof-like) in switch. If you target the latest LTS and higher, you can branch by type without manual casts.
Gotcha: switch on String throws NullPointerException if the value is null. Protect it:
switch (String.valueOf(status)) { /* … */ }


2) Looping (Iteration) Statements
2.1 for loop (classic counter-based)
Best when you know the number of iterations or need the index.
for (int i = 0; i < 5; i++) {
System.out.print(i + ” “); // 0 1 2 3 4
}
Custom step / reverse:
for (int i = 10; i >= 0; i -= 2) {
// 10 8 6 4 2 0
}
2.2 Enhanced for-each loop
Ideal for iterating collections/arrays when you don’t need the index.
List<String> fruits = List.of(“Apple”, “Banana”, “Mango”);
for (String f : fruits) {
System.out.println(f);
}
Pitfall: You can’t modify a list’s structure safely here (e.g., remove items). Use an Iterator if you need removals.
2.3 while loop
Runs while the condition is true; checks before the first iteration.
int n = 3;
while (n > 0) {
System.out.println(n–);
}
// prints 3 2 1
2.4 do-while loop
Guarantees at least one execution; condition checked after the body.
int tries = 0;
do {
tries++;
System.out.println(“Attempt ” + tries);
} while (tries < 3);
2.5 Choosing the right loop (quick guide)
Use case | Best choice | Why |
---|---|---|
Known count / need index | for | Index control & custom steps |
Iterate items only | for-each | Clean and concise |
Unknown repetitions, pre-check | while | Loop until condition fails |
Must run at least once | do-while | Post-condition check |




3) Jump (Branching) Statements
3.1 break
In loops: exits the nearest loop.
In switch statement: prevents fall-through (classic switch).
for (int i = 0; i < 10; i++) {
if (i == 3) break;
System.out.print(i + ” “); // 0 1 2
}
3.2 continue
Skips to the next iteration of the nearest loop.
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) continue;
System.out.print(i + ” “); // 1 3
}
3.3 return
Exits the current method (optionally returning a value).
static int safeDivide(int a, int b) {
if (b == 0) return 0; // guard clause
return a / b;
}


4) Labeled break and continue (for nested loops)
Labels help you break out of or continue an outer loop from inside an inner one. Use sparingly—they can reduce readability if overused.
search:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 2) {
System.out.println(“Found at (” + i + “,” + j + “)”);
break search; // exits both loops
}
}
}
outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) continue outer; // jump to next i
System.out.println(“i=” + i + “, j=” + j);
}
}

6) Common Pitfalls & Best Practices
- Forgotten break in classic switch → unintended fall-through. Prefer switch expressions with ->.
- Null in switch on String → NullPointerException. Guard against nulls.
- Infinite loops → ensure your loop variable changes and the condition will eventually be false.
- Modifying collections in for-each → ConcurrentModificationException. Use an Iterator:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (shouldRemove(it.next())) it.remove();
}
Deep nesting → refactor to methods and use guard clauses:
if (!isValid(user)) return;
// proceed with core logic
Prefer clarity over cleverness. Readable control flow beats micro-optimizations.
7) Quick Reference (Cheat Sheet)
Construct | Purpose | Key Points |
---|---|---|
if / else if / else | Conditional branching | Use short-circuiting; avoid deep nesting |
switch statement | Multi-way branching | Needs break ; supports int , enum , String |
switch expression | Value-returning branch | No fall-through; supports -> and yield |
for | Counter-based loop | Index access; custom step sizes |
for-each | Collection/array loop | Read-only iteration; no index |
while | Pre-check loop | Run zero or more times |
do-while | Post-check loop | Runs at least once |
break / continue | Loop control | Affects nearest loop; consider labels sparingly |
return | Exit method | Use guard clauses for clarity |
8) Practice Exercises (Great for Readers)
- FizzBuzz with a twist: Print numbers 1–100; use a switch expression to produce “Fizz”, “Buzz”, “FizzBuzz”, or the number.
- Min/Max scanner: Given a list of integers, use for-each to compute min and max. Then rewrite with a classic for.
- Matrix search: Write a nested loop to find the first zero in a 2D array. Exit early with a labeled break.
- Login attempts: Use a do-while to simulate up to 3 login tries; stop early on success with break.
9) FAQs
Q: Should I still use classic switch?
A: Prefer switch expressions for new code—they’re safer (no fall-through) and return values cleanly. Classic switch is fine when targeting older JDKs.
Q: When should I use do-while?
A: When the body must run at least once, e.g., prompting for input then validating.
Q: Are labels bad?
A: They’re powerful for nested loops but can hurt readability. Use sparingly and name labels meaningfully (search, outer, etc.).
10) Summary
Java’s control statements give you precise control over program flow. Choose the simplest construct that communicates intent:
- if/switch for decisions (prefer modern switch expressions)
- for/for-each/while/do-while for iteration
- break/continue/return—and labels—when you need to jump
Add guard clauses, avoid deep nesting, and write the branch that’s most common first for readability.