Control Flow

Conditionals

if/else picks a path from a boolean, and an else-if chain picks one of many, in order. A switch statement selects on a value and falls through without break. A switch expression (arrow form) returns a value, never falls through, and must be exhaustive. The ternary ?: is a conditional that yields a value. Every branch is shown with verified output, including the missing-braces, misordered-chain and fall-through traps.

Suggest an edit

Conditionals — Choosing a Path

A program that always does the same thing is a calculator with one button. Conditionals let it choose: run this code when a condition holds, and that code otherwise. Every choice rests on a boolean, the yes/no type from the last chapter. Java gives you four shapes for branching:

  • if/else for one decision;
  • else if chains for many;
  • switch for selecting among constant values;
  • the ternary ?: for a choice that produces a value.

The newest shape, the switch expression, fixes the oldest switch trap by design.

💡 The core idea.

  • Conditionals let a program choose which code to run.
  • Every choice rests on a boolean.
  • Java offers four shapes: if/else, else if chains, switch, and the ternary ?:.

Every output below was produced by compiling and running the code.

You'll be able to: predict which branch of an if/else if chain runs, and order a chain so the narrow test comes first; name the statement that a braceless if, a stray semicolon or a dangling else controls, and fix it with braces; predict what a switch statement prints with and without break, and name the types a switch accepts; write a switch expression with several labels per case and a yield, and explain why it must cover every value; pick ?: to choose a value, and predict its type when the two branches differ.

📘 How to read the Intuition boxes. Each one is built in three moves:

  1. The mechanism — what the compiler and the JVM do.
  2. A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
  3. The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.

Table of contents

  1. if and else
  2. else if chains
  3. The switch statement
  4. switch expressions
  5. The ternary operator ?:
  6. Mental-model summary
  7. Gotcha checklist
  8. Check yourself
  9. Sources

1. if and else

An if runs its block only when a boolean condition is true. An optional else runs when it's false.

Output:

cold

Analysis. temp < 10 is true, so the if block ran and the else block was skipped. Exactly one of the two blocks runs, never both.

The condition must be a boolean [1]. Booleans & Logic promised that Java refuses a number, and here's the refusal:

Compiler error:

Main.java:4: error: incompatible types: int cannot be converted to boolean
        if (count) {
            ^
1 error

Write the question out: if (count != 0).

Intuition. Mechanism. if (cond) { … } guards a block: everything inside the braces. Without braces, if (cond) stmt; guards only the single statement right after it. The next statement isn't part of the if at all, however it's indented.

Concrete bite. That single-statement rule, combined with misleading indentation, is a classic trap:

Output:

leave the house

cold is false, so wear a coat is correctly skipped, but leave the house printed anyway. The indentation suggests both lines are guarded, but only the first one is. The braceless if controls exactly one statement, and the second runs no matter what.

Non-example: a stray semicolon. A ; on its own is a statement too. It's the empty statement, and it does nothing [3]. Put one after the condition, and the if guards that nothing:

Output:

cold

25 < 10 is false, yet cold printed anyway. The if ended at the ;, so the braces after it form an ordinary block that always runs.

The dangling else. With two braceless ifs, an else belongs to the nearest if, whatever the indentation claims [2]:

Output:

done

The indentation says a non-member pays full price. But the else actually belongs to if (sale), and that inner if sits inside if (member). member is false, so neither line ran. Braces put the else where the indentation meant it to be:

Output:

full price

💡 Earned rule. Always use braces, even for a one-line body: if (cond) { … }. It costs you two extra characters.

The payoff: "what does this if control?" has one answer, the block. Without braces, the answer depends on where the semicolons fall and which if is nearest. That's how a guarded line and an always-run line end up looking identical.


2. else if chains

To choose among more than two paths, chain else if. Java tests each condition in order, runs the first block whose condition is true, and skips the rest.

else if isn't a keyword of its own. It's an else whose one statement happens to be another if [1]. So a chain is really ifs nested inside elses, written flat.

Output:

B

Analysis. score is 85. The chain checks >= 90 (false), then >= 80 (true), so it prints B. It skips the remaining tests, including >= 70, which would also have been true. The first match wins, and the rest never run.

This chain leaves out the braces so each branch fits on one line. Every branch is a single statement, so nothing can dangle. Add a second statement to any branch, though, and you'll need the braces.

Intuition. Mechanism. Java evaluates the chain top to bottom and runs the block of the first true condition. Once one matches, the whole chain is done. So Java reaches a later condition only when every earlier one was false. In other words, order encodes priority.

Concrete bite. Put a broad condition before a narrow one, and the narrow one becomes unreachable:

Output:

C

95 deserves an A, but score >= 70 is checked first and matches. So it prints C and never reaches >= 90. The compiler sees nothing wrong. The logic is legal, just ordered backwards.

Non-example: a chain with no final else. A chain often sets a variable. Without an else, some values set nothing, and the compiler spots that path [10]:

Compiler error:

Main.java:10: error: variable grade might not have been initialized
        System.out.println(grade);
                           ^
1 error

score is 85 here, so grade would be "B". But the compiler doesn't run the program. It checks every path, and a score below 80 passes through both tests with grade unset.

This is the rule from Variables & Primitive Types: a variable must have a value before you read it. A final else { grade = "F"; } closes the gap.

💡 Earned rule. In an else if chain, order conditions from most specific (narrowest) to least, because the first match wins. End a chain that sets a variable with an else.

A misordered chain costs you a silent misclassification, not an error. When ranges overlap, read the chain as "first true wins", and put the tightest test on top.


3. The switch statement

When you're choosing by matching one value against several constants, a switch reads more clearly than a long chain.

  • Each case labels a constant.
  • break ends the chosen branch.
  • default catches everything else.

Output:

Wed

Analysis. day is 3, so execution jumped to case 3, printed Wed, and break ended the switch. The other cases were skipped entirely.

Intuition. Mechanism. A switch statement jumps to the matching case label and then runs straight down until it hits a break (or the end) [5]. The break isn't decoration. Without it, execution "falls through" into the next case's code and ignores that case's label.

Concrete bite. Drop the breaks, and one match runs several cases:

Output:

Mon
Tue
Wed

day is 1, so it entered case 1 and printed Mon. Then, with no break, it fell through into case 2 (Tue) and case 3 (Wed), stopping only at the break. One value, three lines printed.

What a switch can test. The value in switch ( … ) is the selector. It can be a char, byte, short or int, or an object such as a String [4]. A String case matches with .equals, so upper and lower case matter:

Output:

halted
unknown: Stop

Java refuses a long, float, double or boolean selector:

Compiler error:

Main.java:4: error: primitive patterns are a preview feature and are disabled by default.
        switch (id) {
               ^
  (use --enable-preview to enable primitive patterns)
Main.java:5: error: constant label of type int is not compatible with switch selector type long
            case 1: System.out.println("one"); break;
                 ^
2 errors

The first message hints at where Java is heading. JDK 25 has a preview feature that would allow these selectors [12]. A preview feature isn't part of the standard language yet: it only works with the --enable-preview flag, and it can still change. Without that flag, a long selector is an error.

Each case label must also be a constant: a literal, or a final variable set from one [4]. Put an ordinary variable after case, and you get constant expression required.

💡 Earned rule. End every case of a switch statement with break (or return) unless you deliberately want fall-through.

This footgun is exactly what the colon-and-break form costs you. A forgotten break is silent and runs too much code. The arrow form in the next section gets rid of it.


4. switch expressions

Since JDK 14, a switch can be an expression that produces a value, using case … -> arrows [8]. There's no fall-through, and you can assign the whole thing to a variable.

Output:

Wed

Analysis. The switch (day) { … } evaluated to "Wed", and that went into name. Each arrow case yields a value, and only that case runs. No break needed, and no fall-through possible. The trailing ; ends the assignment statement.

One case can list several values, separated by commas. And the arrow form works in a plain switch statement too, one that produces no value:

Output:

weekend
sleep in

When a case needs more than one expression, give it a block in braces. The block hands back its value with yield [7]:

Output:

rest (weekend)

Forget the yield, and the block ends with no value to hand back:

Compiler error:

Main.java:7: error: switch rule completes without providing a value
            }
            ^
  (switch rules in switch expressions must either provide a value or throw)
1 error

Intuition. Mechanism. An arrow case X -> value runs only its right-hand side and produces a value for the whole switch. Because the result is used (assigned, returned, printed), the compiler insists that the switch is exhaustive. Every possible input must be covered, or it won't compile [6].

Concrete bite. Leave a gap with no default, and the compiler refuses:

Compiler error:

Main.java:4: error: the switch expression does not cover all possible input values
        String name = switch (day) {
                      ^
1 error

An int can be more than 1 or 2, and a switch expression must always produce a value. So the missing cases are a compile error, not a run-time surprise. Add a default ->, and it compiles.

💡 Earned rule. Prefer arrow switch expressions when you're computing a value from one of several cases. They can't fall through, and the compiler checks exhaustiveness for you.

The cost is that you must handle every case (or add a default). But that's the whole point: the compiler turns "I forgot a branch" from a run-time bug into a build error. Java 21 extends this to sealed types and pattern matching [11], in Sealed Classes & Pattern Matching.


5. The ternary operator ?:

For the smallest choice, picking one of two values based on a condition, use the ternary operator cond ? a : b. It's a conditional that is itself an expression: it evaluates to a when cond is true, and to b otherwise [9].

Output:

adult
7

Analysis. age >= 18 is true, so the ternary produced "adult". In the second, a > b is true, so it produced a (7), a one-line "max". Because the ternary yields a value, it can sit on the right of = or inside a println.

When the two values are different numeric types, the result takes the wider type, just as in arithmetic [9]. Even the branch that was chosen changes type:

Output:

1.0

The condition chose 1, and 1 became 1.0, because the whole expression has one type, double.

Intuition. Mechanism. ?: is an expression, so it has a value. if is a statement, so it doesn't. That's why you can assign or print a ternary directly, but not an if.

Concrete bite. Try to use if where a value is required, and it fails to compile, because a statement has no value to assign:

Compiler error (first of several as the parser recovers):

Main.java:4: error: illegal start of expression
        String s = if (cond) "yes" else "no";
                   ^

if can't start an expression, so Java rejects String s = if …. The ternary String s = cond ? "yes" : "no"; is the expression form that works.

Non-example: a ternary inside a + chain. ?: binds more loosely than + and every comparison. Without parentheses, the text joins the number first:

Compiler error:

Main.java:4: error: bad operand types for binary operator '>='
        System.out.println("status: " + age >= 18 ? "adult" : "minor");
                                            ^
  first type:  String
  second type: int
1 error

"status: " + age became the text "status: 20", and >= can't compare text with 18. Wrap the ternary: "status: " + (age >= 18 ? "adult" : "minor") prints status: adult.

💡 Earned rule. Reach for ?: to choose between two values in one expression (max = a > b ? a : b), and if to choose between two actions. Put a ternary in parentheses whenever it sits inside a longer expression.

?: costs you readability. Nested ternaries (a ? b : c ? d : e) get unreadable fast. Keep each one to a single, simple choice, and use if/else or a switch expression for anything branchier.


6. Mental-model summary

Principle Consequence
if guards a block; braceless if guards one statement Always use braces, or an "indented" line runs unconditionally
; is a statement; else belongs to the nearest if if (c); guards nothing; braces decide which if owns an else
An else if chain runs the first true branch, then stops Order conditions narrowest-first; end with else when the chain sets a variable
A switch statement falls through without break A forgotten break runs into the next case — end each with break
A switch tests char, byte, short, int or an object such as String long, double and boolean selectors do not compile; case labels are constants
A switch expression (->) yields a value, no fall-through It must be exhaustive; a block case hands its value back with yield
?: is an expression (has a value); if is a statement Use ?: to choose a value; its type is the wider of the two branches

7. Gotcha checklist

Symptom Likely cause Fix
incompatible types: int cannot be converted to boolean on an if a number as the condition, if (count) if (count != 0)
A line runs even though the if was false no braces: the if guarded only the statement before it add { }
A block runs whatever the condition a ; straight after if ( … ) delete the ;
An else runs for the wrong case, or never a dangling else attached to the nearest if braces around the inner if
A value gets the wrong branch in an else if chain a broader condition listed before a narrower one reorder narrowest-first
variable grade might not have been initialized after a chain some path through the chain sets nothing add a final else that sets it
cannot find symbol for a variable set inside an if it was declared inside the block, so it ends with the block declare it before the if
A switch prints several cases for one value missing breaks caused fall-through add break, or use the arrow form
A String case never matches the text differs in letter case ("Stop" vs "stop") make the cases match the input exactly
selector type long is not allowed a long, double, float or boolean selector an if chain, or an int selector
constant expression required a plain variable after case a literal, or a final constant
the switch expression does not cover all possible input values an arrow switch used as a value isn't exhaustive add the missing cases or a default ->
switch rule completes without providing a value a block case with no yield end the block with yield value;
illegal start of expression on an if if used where a value was needed the ternary cond ? a : b
A ternary prints 1.0 where you expected 1 the other branch is a double, so both are make both branches the same type

✅ Check yourself

One check per objective. Answer before you open anything.

The 🧪 box below: the grade chain for 90, 70 and 69; the §3 switch for day = 2 with and without break; and the chain as a switch expression.

The chain prints A for 90, C for 70 and F for 69.

With every break, day = 2 prints Tue. With every break removed, it falls through to the end: Tue, Wed, other.

A case needs a constant, not a range, so switch on the tens digit instead. score / 10 is integer division, so 85 becomes 8:

String grade = switch (score / 10) { case 10, 9 -> "A"; case 8 -> "B"; case 7 -> "C"; default -> "F"; };

It prints A for 100 and 90, B for 85, C for 70, and F for 69. The default is the fail grade, and it keeps the switch exhaustive. It also catches bad input: -5 gives F.


📚 Sources

  1. The Java Language Specification, Java SE 25, §14.9 "The if Statement" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.9
  2. The Java Language Specification, Java SE 25, §14.5 "Statements" (the dangling else: "an else clause belongs to the innermost if to which it might possibly belong") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.5
  3. The Java Language Specification, Java SE 25, §14.6 "The Empty Statement" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.6
  4. The Java Language Specification, Java SE 25, §14.11 "The switch Statement" (selector types; case constants; String cases use equals) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.11
  5. The Java Language Specification, Java SE 25, §14.11.3 "Execution of a switch Statement" (fall through) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.11.3
  6. The Java Language Specification, Java SE 25, §15.28.1 "The Switch Block of a switch Expression" ("It is a compile-time error if a switch expression is not exhaustive") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.28.1
  7. The Java Language Specification, Java SE 25, §14.21 "The yield Statement" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.21
  8. JEP 361: Switch Expressions (JDK 14) — https://openjdk.org/jeps/361
  9. The Java Language Specification, Java SE 25, §15.25 "Conditional Operator ? :" and §15.25.2 "Numeric Conditional Expressions" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.25
  10. The Java Language Specification, Java SE 25, Chapter 16 "Definite Assignment" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-16.html
  11. JEP 441: Pattern Matching for switch (JDK 21) — https://openjdk.org/jeps/441
  12. JEP 507: Primitive Types in Patterns, instanceof, and switch (Third Preview) (JDK 25) — https://openjdk.org/jeps/507

🧪 Predict, then check.

  1. For the §2 grade chain, predict the printed letter for score = 90, then score = 70, then score = 69.
  2. Take the §3 switch and predict the output for day = 2 with the breaks, and again without them.
  3. Rewrite the grade chain as a switch expression that assigns the letter to a String and prints it. Decide what its default should be. Build it and confirm.

Your Turn

Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.

Mark as readBook versionfd26460