Key Takeaways
- Java control flow statements choose which code runs, repeat work, and transfer control out of loops or methods.
- Use
if,else if, andelsefor boolean conditions; useswitchwhen a multi-way choice over one value reads more clearly. - Traditional
case:labels allow fall-through. Arrow rules avoid it, and switch expressions produce a value. - A
whileloop can run zero times; ado-whileloop runs its body at least once. Enhancedforloops traverse elements without an index. breakexits a loop or switch statement,continueskips the rest of an iteration, andreturnexits a method.
Java control flow statements direct program execution through decisions, loops, and branches instead of simply running every statement in order. They let you choose an action, repeat it while a condition holds, or stop processing when you've found a result.
This tutorial assumes you're comfortable with Java primitive data types and Java arrays.
What Is a Control Flow Statement?
Start with a decision: print a message only when a number is less than 10.
public class ControlFlowDemo {
public static void main(String[] args) {
int i = 5;
if (i < 10) {
System.out.println("i is less than 10");
}
}
}Because 5 < 10 is true, this prints i is less than 10.
Save the example as ControlFlowDemo.java. With a JDK installed and java available in your terminal, run it using source-file mode:
java ControlFlowDemo.javaThe Java launcher compiles and runs the source file. Each following snippet marked as a fragment replaces the contents of main; run the fragments separately rather than pasting them together.
Decision-Making Statements
if: Run Code When a Condition Is True
An if statement executes its body when its condition evaluates to true. Here, the condition is already a boolean variable:
// Fragment: replace the body of main.
boolean isActive = true;
if (isActive) {
System.out.println("Your account is active");
}This prints Your account is active. Java permits a single statement without braces, but use braces consistently: adding another statement later shouldn't silently change what the condition controls.
if-else: Choose Between Two Paths
The else body runs when the if condition is false.
// Fragment: replace the body of main.
boolean isActive = false;
if (isActive) {
System.out.println("Your account is active");
} else {
System.out.println("Your account is inactive");
}This prints Your account is inactive. An inactive account isn't necessarily a missing account, so keep your message aligned with what the condition actually establishes.
else if: Handle Several Conditions
An else if chain tests conditions in order and executes only the first matching branch. The final else handles everything left over.
// Fragment: replace the body of main.
int score = 82;
if (score >= 90) {
System.out.println("Excellent");
} else if (score >= 70) {
System.out.println("Passed");
} else {
System.out.println("Try again");
}This prints Passed. Put the narrower threshold first: checking score >= 70 before score >= 90 would also catch scores of 90 and above.
Traditional switch: Understand Fall-Through and break
A switch selector is the value being examined. For choices among known constants, a traditional switch statement can make the alternatives easy to scan:
// Fragment: replace the body of main.
int number = 2;
switch (number) {
case 1:
System.out.println("The number is 1");
break;
case 2:
System.out.println("The number is 2");
System.out.println("That's an even number!");
break;
case 3:
System.out.println("The number is 3");
break;
default:
System.out.println("Pick a number between 1 and 3");
break;
}This prints the two messages under case 2. If you remove its break, execution continues into case 3 and also prints The number is 3, even though the selector is still 2. That's fall-through: execution continues through subsequent statements without testing the next label again.
The default branch handles an unmatched number. Without a matching case or a default, this traditional integer switch statement does nothing.
Arrow Rules and Switch Expressions
Prefer arrow rules for new switch code unless you specifically need fall-through. An arrow rule executes its selected branch without continuing into the next rule, so it doesn't need a terminating break.
A switch expression produces a value, which you can assign directly:
// Fragment: replace the body of main.
int number = 2;
String message = switch (number) {
case 1 -> "The number is 1";
case 2 -> {
System.out.println("That's an even number!");
yield "The number is 2";
}
case 3 -> "The number is 3";
default -> "Pick a number between 1 and 3";
};
System.out.println(message);A single expression after the arrow supplies the branch's value. A block can contain multiple statements and uses yield to supply that value; yield doesn't exit the enclosing method. This example prints the even-number message, followed by The number is 2.
Switch expressions must be exhaustive, meaning they cover every possible selector value. Here, default covers the remaining integers. Notice the final semicolon: the switch expression is part of a variable declaration.
Arrow syntax and value production are separate choices: arrow rules also work in switch statements, and colon labels also work in switch expressions. Oracle's switch expressions and statements guide explains both forms.
For more background on the feature's introduction, see Java 14 features.
Pattern Matching for switch
Modern switch isn't limited to equality checks against constants. Pattern matching lets a case check an object's type and bind a variable you can use in that branch:
// Fragment: replace the body of main. Requires Java 21+.
Object value = "hello";
String description = switch (value) {
case null -> "No value";
case String text -> "Text: " + text;
default -> "Another type";
};
System.out.println(description);This prints Text: hello. The String text pattern matches a string and makes it available as text. Here, case null handles null explicitly; default alone doesn't handle a null selector. Without a null case, a null reference selector throws NullPointerException. See the pattern matching guide when you're ready for more complex cases.
Java Loops: Repeat Work
while: Check Before Each Iteration
A while loop checks its condition before executing its body each time. It can run zero times if the initial condition is false, or indefinitely if the condition stays true and nothing exits the loop.
// Fragment: replace the body of main.
int count = 1;
while (count < 10) {
System.out.println(count);
count++;
}This prints 1 through 9. Forgetting count++ creates an infinite loop in this example because count stays at 1.
do-while: Run the Body Before Checking
A do-while loop executes its body before evaluating its condition, so the body runs at least once.
// Fragment: replace the body of main.
int count = 11;
do {
System.out.println(count);
count++;
} while (count < 10);This prints 11 once, increments the counter to 12, and stops. The condition would have been false even before the first iteration, but it wasn't tested until afterward. Unlike a while loop, the do-while syntax requires the trailing semicolon.
while: condition? --true--> body --> condition?
|
false --> exit
do-while: body --> condition? --true--> body
|
false --> exitClassic for: Keep Counter Control Together
The classic for loop groups initialization, a condition, and an update in its header:
// Fragment: replace the body of main.
for (int i = 1; i < 6; i++) {
System.out.println(i);
}This prints 1 through 5. The execution order is:
- Initialize
ito 1 once. - Check
i < 6before each iteration. - Execute the body when the condition is true.
- Execute
i++after the body completes normally, then check again.
Use this form when you need a counter, an index, or an explicit stepping rule. Declaring i in the header keeps it scoped to the loop.
Enhanced for: Traverse Elements Without an Index
The enhanced for loop, often called for-each, visits elements of an array or an object implementing Iterable, including collections such as lists.
// Fragment: replace the body of main.
int[] numbers = {10, 20, 30};
for (int number : numbers) {
System.out.println(number);
}This prints 10, 20, and 30. Prefer it when you only need each element; use an indexed loop when you need positions or want to replace array elements. Assigning a new value to this loop's primitive variable number doesn't change the array element.
Branching Statements: break, continue, and return
break: Exit the Nearest Loop or Switch Statement
An unlabeled break exits the innermost enclosing loop or switch statement. Execution then continues after that construct.
// Fragment: replace the body of main.
for (int i = 1; i < 5; i++) {
System.out.println(i);
if (i > 2) {
break;
}
}This prints 1, 2, and 3. Printing happens before the check, so 3 appears before the loop exits.
continue: Skip the Rest of an Iteration
An unlabeled continue skips the remaining body of the innermost loop, not the entire loop.
// Fragment: replace the body of main.
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue;
}
System.out.println(i);
}This prints 0, 1, 3, and 4; only 2 is skipped. In a classic for loop, continue still runs the header's update before checking the condition again. In while and do-while, it proceeds to the condition check.
Labeled break and continue: Control Nested Loops
A label names an enclosing statement. Use a labeled break when finding a result should exit both an inner loop and an outer loop:
// Fragment: replace the body of main.
int[][] grid = {{1, 2}, {3, 4}};
search:
for (int[] row : grid) {
for (int value : row) {
if (value == 3) {
System.out.println("Found 3");
break search;
}
}
}
System.out.println("Search finished");This prints Found 3, then Search finished. The labeled break exits the statement labeled search; it doesn't jump backward to the label.
A labeled continue instead skips the current iteration of the named enclosing loop. Here, it rejects any row containing a negative number:
// Fragment: replace the body of main.
int[][] rows = {{1, 2}, {-1, 4}, {5, 6}};
outer:
for (int[] row : rows) {
for (int value : row) {
if (value < 0) {
continue outer;
}
}
System.out.println("Accepted row");
}This prints Accepted row twice. A labeled continue must target an enclosing loop; a labeled break can also exit an enclosing labeled block. Reserve labels for cases where they make the nested logic clearer than an extracted method would.
return: Exit a Method
A return exits the current method, even when it appears inside nested loops. In a value-returning method, it also supplies a result compatible with the method's declared return type.
Save this separate, complete example as ReturnExample.java and run java ReturnExample.java:
public class ReturnExample {
static int firstIndexOf(int[] numbers, int target) {
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == target) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] numbers = {4, 7, 9};
System.out.println(firstIndexOf(numbers, 7));
}
}This prints 1, the zero-based index of 7. The helper returns immediately on a match; if no match exists, it returns -1. The example assumes the array isn't null. In a void method, use return; without a value.
Common Mistakes
- Skipping a counter update: In a
whileloop, acontinuebefore your counter update can prevent progress. Prefer a classicforloop for counter-controlled work. - Using the wrong array boundary: Valid indexes run from 0 through
length - 1. Usei < numbers.length, noti <= numbers.length. - Leaving an accidental semicolon: In
if (condition);orwhile (condition);, the semicolon is an empty body, not punctuation before the intended block. - Assuming break exits every loop: An unlabeled break exits only its nearest enclosing loop or switch statement.
- Overusing branches: Prefer clear loop conditions and small methods over a maze of breaks, continues, and labels. Choose switch for clarity, not assumed speed.
When testing, include a false initial condition, an empty array, boundary values, and both matched and unmatched inputs. These cases exercise paths that a single successful example won't cover.
FAQ
What are control flow statements in Java?
Java control flow statements choose which code executes, repeat work, or transfer control. The main beginner constructs are if-else and switch for decisions, for and while loops for repetition, and break, continue, and return for branching.
What is the difference between while and do-while in Java?
A while loop checks its condition before each iteration, so its body may never execute. A do-while loop executes its body before checking the condition, so the body runs at least once.
When should I use switch instead of if-else in Java?
Use switch when a multi-way choice over one selector is clearer, especially for known constants or type patterns. Use if-else for ranges, unrelated predicates, or conditions that read more naturally as boolean expressions; don't assume switch is faster.
How do traditional and arrow-style Java switches differ?
Traditional case: labels allow fall-through unless execution exits, commonly with break. Arrow rules don't fall through and can be used in either switch statements or value-producing switch expressions; both arrow rules and switch expressions became permanent in Java 14.
What is the difference between break, continue, and return in Java?
An unlabeled break exits the innermost loop or switch statement, while an unlabeled continue skips the rest of the current iteration of the innermost loop. A return exits the current method and may supply a value; labels let break and continue target enclosing constructs explicitly.
Further Reading
- Dev.java: Control Flow Statements — decisions, loops, and branching fundamentals.
- Oracle: Switch Expressions and Statements — arrow rules, yield, and exhaustiveness.
- Oracle: Pattern Matching with switch — type patterns, ordering, and null handling.
- Oracle: Java Language Changes — when switch features became permanent.
- Java Language Specification: Statements — precise rules for conditions, loops, labels, and control transfers.
- Java Launcher Reference — running examples directly from source files.
