Welcome back, aspiring Java developer! In our previous chapters, we learned how to set up our Java environment, write our first basic program, and handle different types of data with variables. That’s a fantastic start! But what if your program needs to do different things based on certain conditions? Or what if you need to perform the same action multiple times without writing the same code over and over?

That’s exactly what we’ll tackle in this chapter: Control Flow. Think of it as teaching your program how to “think” and “do.” We’ll explore how to make decisions using if statements and switch statements, and how to repeat actions efficiently using various types of loops. Mastering control flow is absolutely fundamental to writing any useful program, transforming your code from a simple script into a dynamic, interactive application. Get ready to give your Java programs some serious intelligence!

1. Introduction to Control Flow

Imagine you’re giving instructions to a robot. Sometimes you tell it, “If the light is green, cross the street.” Other times, you might say, “Keep walking until you reach the wall.” These are examples of control flow in real life: making decisions and repeating actions.

In programming, control flow statements dictate the order in which individual statements or instructions are executed. Without them, our programs would just run from top to bottom, executing every line exactly once. With control flow, we can introduce logic, make our programs flexible, and handle complex scenarios.

This chapter assumes you’re comfortable with:

  • Setting up your Java Development Kit (JDK 25).
  • Creating and running a basic Java program.
  • Declaring and using variables with primitive data types (like int, double, boolean, String).

Let’s dive in and empower your code!

2. Core Concepts: The Brains and Brawn of Your Code

Control flow can be broadly categorized into two main types:

  1. Conditional Statements: These allow your program to make decisions based on whether a condition is true or false.
  2. Looping Statements: These enable your program to repeat a block of code multiple times.

2.1 Conditional Statements: Making Decisions

Java provides several ways to make decisions. Let’s explore them.

2.1.1 The if Statement: Your First Decision

The if statement is the most basic way to introduce decision-making. It executes a block of code only if a specified condition is true.

What it is: A keyword that checks a boolean expression. Why it’s important: It’s the simplest way to execute code conditionally. How it functions: If the condition inside the parentheses () is true, the code inside the curly braces {} is executed. Otherwise, it’s skipped.

// Basic if statement structure
if (condition) {
    // Code to execute if the condition is true
}
2.1.2 The if-else Statement: Handling Two Paths

Often, if a condition isn’t met, you want to do something else. That’s where if-else comes in.

What it is: An extension of if that provides an alternative path. Why it’s important: Allows your program to handle two distinct outcomes for a single condition. How it functions: If the if condition is true, the if block executes. If it’s false, the else block executes. Only one of the two blocks will ever run.

// if-else statement structure
if (condition) {
    // Code to execute if the condition is true
} else {
    // Code to execute if the condition is false
}
2.1.3 The if-else if-else Ladder: Multiple Choices

When you have more than two possible outcomes, an if-else if-else ladder is your go-to.

What it is: A sequence of if and else if statements, optionally ending with an else. Why it’s important: Handles multiple mutually exclusive conditions gracefully. How it functions: Java checks each if or else if condition in order. The first condition that evaluates to true has its corresponding block executed, and then the rest of the ladder is skipped. If no if or else if condition is true, the final else block (if present) is executed.

// if-else if-else ladder structure
if (condition1) {
    // Code if condition1 is true
} else if (condition2) {
    // Code if condition2 is true
} else if (condition3) {
    // Code if condition3 is true
} else {
    // Code if none of the above conditions are true
}
2.1.4 The switch Statement: Choosing from Fixed Values (Modern Java 25!)

The switch statement is perfect when you need to choose among a fixed set of values for a single variable. With modern Java, specifically from JDK 14 onwards (and fully available in our target JDK 25!), switch statements have become much more powerful and concise.

What it is: A control flow statement that allows a variable to be tested for equality against a list of values. Why it’s important: Provides a cleaner, more readable alternative to a long if-else if-else ladder when dealing with specific, discrete values (like int, char, String, enums). How it functions: The value of an expression is compared to the values of each case label. When a match is found, the associated code block is executed.

Traditional switch (pre-Java 14): Required break statements to prevent “fall-through” (where execution continues into the next case). This was often a source of bugs.

Modern switch Expressions (JDK 25): Java 25 supports switch expressions, which are much safer and more concise. They use the -> operator (arrow syntax) and can even return a value. This eliminates the need for break statements and accidental fall-through! If you need to execute multiple statements in a case and still return a value, you can use yield.

// Modern switch expression structure (JDK 25)
String dayType = switch (dayOfWeek) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    case SATURDAY, SUNDAY -> "Weekend";
    default -> "Invalid day"; // The default case is often required for exhaustiveness
};

// Example with a block and yield (if more complex logic is needed)
int result = switch (value) {
    case 1 -> {
        System.out.println("Processing value 1");
        yield 10; // yield returns the value from the switch expression
    }
    case 2 -> 20;
    default -> 0;
};

Notice how much cleaner this is! No more break statements to forget.

2.2 Looping Statements: Repeating Actions

Loops allow you to execute a block of code repeatedly until a certain condition is met.

2.2.1 The while Loop: Repeat While True

The while loop is the simplest looping construct. It repeats a block of code as long as its condition remains true.

What it is: A loop that executes a block of code zero or more times. Why it’s important: Useful when you don’t know in advance how many times you need to loop, but you have a clear condition for stopping. How it functions: The condition is evaluated before each iteration. If true, the loop body executes. If false, the loop terminates. If the condition is initially false, the loop body never runs.

// while loop structure
while (condition) {
    // Code to repeat as long as the condition is true
    // Make sure something inside changes the condition to eventually become false!
}
2.2.2 The do-while Loop: Guaranteed at Least Once

The do-while loop is similar to while, but it guarantees that the loop body executes at least once before the condition is checked.

What it is: A loop that executes a block of code one or more times. Why it’s important: Ideal when you need to perform an action at least once, regardless of the initial condition (e.g., getting user input and then validating it). How it functions: The loop body executes first, then the condition is evaluated. If true, the loop repeats. If false, the loop terminates.

// do-while loop structure
do {
    // Code to repeat (will execute at least once)
    // Make sure something inside changes the condition to eventually become false!
} while (condition);
2.2.3 The for Loop: Controlled Repetition

The for loop is typically used when you know exactly how many times you want to loop, or when you need a clear counter.

What it is: A loop that provides a concise way to write a loop with initialization, condition, and increment/decrement in a single line. Why it’s important: Excellent for iterating a fixed number of times, or over sequences (like arrays, which we’ll see later). How it functions:

  1. Initialization: Executed once at the beginning of the loop. Often used to declare and initialize a loop counter.
  2. Condition: Evaluated before each iteration. If true, the loop body executes. If false, the loop terminates.
  3. Increment/Decrement: Executed after each iteration. Typically used to update the loop counter.
// for loop structure
for (initialization; condition; increment/decrement) {
    // Code to repeat
}
2.2.4 The Enhanced for Loop (for-each): Simple Iteration

Introduced in Java 5, the enhanced for loop (often called “for-each”) simplifies iterating over collections and arrays. While we haven’t covered arrays yet, it’s good to know this exists for future use.

What it is: A simplified for loop syntax for iterating over elements of arrays and collections. Why it’s important: Makes code cleaner and less error-prone when simply iterating through all elements. How it functions: It iterates through each element in the iterable object (e.g., an array), assigning the current element to a temporary variable in each iteration.

// Enhanced for loop (for-each) structure
// (Imagine 'numbers' is an array or list of integers)
for (int number : numbers) {
    // Code to execute for each 'number' in 'numbers'
}

2.3 Jump Statements: break and continue

Sometimes, you need to modify the normal flow of a loop or a switch statement.

2.3.1 break: Escaping Early

What it is: A keyword that immediately terminates the innermost switch statement or loop. Why it’s important: Useful for exiting a loop early if a specific condition is met, or for preventing fall-through in traditional switch statements (though modern switch expressions make this less necessary). How it functions: When break is encountered, control immediately jumps to the statement following the switch or loop.

2.3.2 continue: Skipping an Iteration

What it is: A keyword that skips the rest of the current iteration of a loop and proceeds to the next iteration. Why it’s important: Useful when you want to bypass certain parts of the loop body for specific conditions but want the loop to continue. How it functions: When continue is encountered, the current iteration’s remaining code is skipped, and the loop’s condition is re-evaluated (for while/do-while) or the increment/decrement step is executed (for for).

3. Step-by-Step Implementation: Bringing Control Flow to Life

Let’s create a Java program to experiment with these control flow statements.

First, make sure you have your project set up from Chapter 2. Inside your src/main/java/com/example/myfirstapp directory (or wherever you put your .java files), create a new file named ControlFlowDemo.java.

// src/main/java/com/example/myfirstapp/ControlFlowDemo.java
package com.example.myfirstapp; // Adjust package name if yours is different

public class ControlFlowDemo {

    public static void main(String[] args) {
        System.out.println("Hello from ControlFlowDemo!");
        // We'll add our code here
    }
}

Now, let’s add our control flow examples step-by-step inside the main method.

Step 3.1: The if and if-else Statements

Let’s start with simple decision-making.

Add this code inside your main method, replacing // We'll add our code here:

        System.out.println("\n--- if and if-else statements ---");

        int temperature = 25; // Let's say it's 25 degrees Celsius

        // Basic if statement
        if (temperature > 20) {
            System.out.println("It's a warm day!");
        }

        // if-else statement
        int score = 75;
        if (score >= 60) {
            System.out.println("You passed the exam!");
        } else {
            System.out.println("You need to study more.");
        }

Explanation:

  • We declare an int variable temperature and set it to 25.
  • The first if checks if temperature is greater than 20. Since 25 > 20 is true, “It’s a warm day!” will be printed.
  • Next, score is 75. The if (score >= 60) condition (75 >= 60) is true, so “You passed the exam!” is printed. The else block is skipped.

Compile and Run: Open your terminal or command prompt, navigate to your project’s root directory (where pom.xml or build.gradle is, or where your src folder is if not using a build tool), and compile:

javac src/main/java/com/example/myfirstapp/ControlFlowDemo.java

Then run:

java -cp src/main/java com.example.myfirstapp.ControlFlowDemo

(If you’re using Maven or Gradle, you can just run mvn compile exec:java -Dexec.mainClass="com.example.myfirstapp.ControlFlowDemo" or gradle run after setting up your build.gradle with the main class.)

You should see:

Hello from ControlFlowDemo!

--- if and if-else statements ---
It's a warm day!
You passed the exam!

Challenge yourself: Change temperature to 15 and score to 50. What output do you expect? Run it and verify!

Step 3.2: The if-else if-else Ladder

Now, let’s handle more complex grading logic.

Add this code after the previous if-else block:

        System.out.println("\n--- if-else if-else ladder ---");

        int grade = 88;
        String letterGrade;

        if (grade >= 90) {
            letterGrade = "A";
        } else if (grade >= 80) { // grade is between 80 and 89
            letterGrade = "B";
        } else if (grade >= 70) { // grade is between 70 and 79
            letterGrade = "C";
        } else if (grade >= 60) { // grade is between 60 and 69
            letterGrade = "D";
        } else { // grade is below 60
            letterGrade = "F";
        }
        System.out.println("Your grade of " + grade + " is a " + letterGrade);

Explanation:

  • We set grade to 88.
  • The first condition (grade >= 90) is false.
  • The second condition (grade >= 80) is true (88 >= 80). So, letterGrade becomes “B”, and the rest of the else if and else blocks are skipped.

Compile and Run: (Use your preferred method) Expected output (in addition to previous):

--- if-else if-else ladder ---
Your grade of 88 is a B

Challenge yourself: Try changing grade to 95, 72, and 55. Predict the output each time before running.

Step 3.3: The switch Statement (Modern JDK 25 Style!)

Let’s use the sleek, modern switch expression to determine the type of a day.

Add this code after the if-else if-else block:

        System.out.println("\n--- Modern switch expression (JDK 25) ---");

        String dayOfWeek = "Wednesday"; // Let's pick a day!
        String dayType = switch (dayOfWeek) {
            case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday";
            case "Saturday", "Sunday" -> "Weekend";
            default -> "Invalid Day"; // Always good to have a default!
        };
        System.out.println(dayOfWeek + " is a " + dayType);

        // Another switch example using 'yield' for more complex cases (optional)
        int dayNumber = 3; // 1 for Mon, 2 for Tue, etc.
        String dayCategory = switch (dayNumber) {
            case 1, 2, 3, 4, 5 -> {
                System.out.println("It's a working day."); // Can have multiple statements
                yield "Busy Day"; // 'yield' returns the value
            }
            case 6, 7 -> {
                System.out.println("Time to relax!");
                yield "Free Day";
            }
            default -> {
                System.out.println("Unknown day number.");
                yield "Unknown";
            }
        };
        System.out.println("Day number " + dayNumber + " falls into category: " + dayCategory);

Explanation:

  • We declare dayOfWeek as “Wednesday”.
  • The switch expression evaluates dayOfWeek. It matches the case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday".
  • The arrow -> indicates that the value “Weekday” is assigned to dayType. No break is needed!
  • In the second example, dayNumber is 3. It matches case 1, 2, 3, 4, 5. The System.out.println executes, and then yield "Busy Day" assigns “Busy Day” to dayCategory.

Compile and Run: Expected output (in addition to previous):

--- Modern switch expression (JDK 25) ---
Wednesday is a Weekday
It's a working day.
Day number 3 falls into category: Busy Day

Why this is better: The modern switch expression is less error-prone because break statements are implicit (or yield explicitly returns a value), preventing accidental fall-through, which was a common bug with traditional switch. It’s also more concise and readable!

Step 3.4: The while Loop

Let’s count up to 5 using a while loop.

Add this code after the switch blocks:

        System.out.println("\n--- while loop ---");

        int count = 1;
        while (count <= 5) {
            System.out.println("Count is: " + count);
            count++; // IMPORTANT: Increment count to eventually make the condition false
        }
        System.out.println("While loop finished. Final count: " + count);

Explanation:

  • count starts at 1.
  • Iteration 1: (1 <= 5) is true. Prints “Count is: 1”. count becomes 2.
  • Iteration 2: (2 <= 5) is true. Prints “Count is: 2”. count becomes 3.
  • Iteration 5: (5 <= 5) is true. Prints “Count is: 5”. count becomes 6.
  • Iteration 6: (6 <= 5) is false. The loop terminates.

Compile and Run: Expected output (in addition to previous):

--- while loop ---
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
While loop finished. Final count: 6

Step 3.5: The do-while Loop

Now, let’s see do-while in action.

Add this code after the while loop:

        System.out.println("\n--- do-while loop ---");

        int i = 0;
        do {
            System.out.println("Do-while iteration: " + i);
            i++;
        } while (i < 3);
        System.out.println("Do-while loop finished. Final i: " + i);

        // Demonstrating do-while running at least once
        int x = 10;
        do {
            System.out.println("This will print once even though x < 5 is false. x is: " + x);
            x++;
        } while (x < 5);
        System.out.println("Second do-while loop finished. Final x: " + x);

Explanation:

  • The first do-while loop: i starts at 0. It prints 0, 1, 2. When i becomes 3, (3 < 3) is false, and the loop stops.
  • The second do-while loop: x starts at 10. The do block executes once, printing “This will print once… x is: 10”. Then x becomes 11. Now the condition (x < 5) (which is 11 < 5) is false, so the loop terminates. This clearly shows it runs at least once.

Compile and Run: Expected output (in addition to previous):

--- do-while loop ---
Do-while iteration: 0
Do-while iteration: 1
Do-while iteration: 2
Do-while loop finished. Final i: 3
This will print once even though x < 5 is false. x is: 10
Second do-while loop finished. Final x: 11

Step 3.6: The for Loop

Let’s use a for loop to count from 0 to 4.

Add this code after the do-while blocks:

        System.out.println("\n--- for loop ---");

        for (int j = 0; j < 5; j++) {
            System.out.println("For loop iteration: " + j);
        }
        System.out.println("For loop finished.");

Explanation:

  • int j = 0; (Initialization): j starts at 0.
  • j < 5; (Condition): The loop continues as long as j is less than 5.
  • j++ (Increment): j increases by 1 after each iteration.
  • This prints “For loop iteration: 0” through “For loop iteration: 4”. When j becomes 5, the condition (5 < 5) is false, and the loop terminates.

Compile and Run: Expected output (in addition to previous):

--- for loop ---
For loop iteration: 0
For loop iteration: 1
For loop iteration: 2
For loop iteration: 3
For loop iteration: 4
For loop finished.

Step 3.7: break and continue Statements

Finally, let’s see how break and continue can alter loop behavior.

Add this code after the for loop:

        System.out.println("\n--- break and continue ---");

        // Example with break
        for (int k = 1; k <= 10; k++) {
            if (k == 5) {
                System.out.println("Breaking loop when k is 5!");
                break; // Exit the loop entirely
            }
            System.out.println("Break loop iteration: " + k);
        }
        System.out.println("Loop with break finished.");

        System.out.println("-------------------------");

        // Example with continue
        for (int m = 1; m <= 5; m++) {
            if (m == 3) {
                System.out.println("Skipping iteration when m is 3!");
                continue; // Skip the rest of this iteration, go to the next
            }
            System.out.println("Continue loop iteration: " + m);
        }
        System.out.println("Loop with continue finished.");

Explanation:

  • break example: The for loop runs from 1 to 10. When k becomes 5, the if (k == 5) condition is true. “Breaking loop when k is 5!” is printed, and then break; immediately stops the entire loop. The System.out.println("Break loop iteration: " + k); for k=5 and subsequent iterations is never reached.
  • continue example: The for loop runs from 1 to 5. When m becomes 3, the if (m == 3) condition is true. “Skipping iteration when m is 3!” is printed, and then continue; makes the loop immediately jump to the next iteration (increment m to 4, then check the condition). The line System.out.println("Continue loop iteration: " + m); is skipped for m=3.

Compile and Run: Expected output (in addition to previous):

--- break and continue ---
Break loop iteration: 1
Break loop iteration: 2
Break loop iteration: 3
Break loop iteration: 4
Breaking loop when k is 5!
Loop with break finished.
-------------------------
Continue loop iteration: 1
Continue loop iteration: 2
Skipping iteration when m is 3!
Continue loop iteration: 4
Continue loop iteration: 5
Loop with continue finished.

Phew! That was a lot of code, but you’ve just implemented the fundamental building blocks of program logic. Give yourself a pat on the back!

4. Mini-Challenge: The FizzBuzz Classic!

Now it’s your turn to combine what you’ve learned. The “FizzBuzz” problem is a common coding interview question and a great way to practice control flow.

Challenge: Write a Java program that prints numbers from 1 to 100.

  • For multiples of three, print “Fizz” instead of the number.
  • For multiples of five, print “Buzz” instead of the number.
  • For numbers which are multiples of both three and five, print “FizzBuzz” instead of the number.
  • Otherwise, print the number itself.

Hint:

  • You’ll need a loop (a for loop is often a good choice here).
  • You’ll need conditional statements (if-else if-else is perfect).
  • Remember the modulo operator (%) from Chapter 2? It gives you the remainder of a division. For example, number % 3 == 0 checks if number is a multiple of 3.
  • Order matters! Think about which condition you should check first. If you check for “multiple of 3” and “multiple of 5” separately before checking for “multiple of both”, you might miss the “FizzBuzz” case.

What to observe/learn:

  • How to combine loops and conditionals.
  • The importance of condition order in if-else if-else ladders.
  • Applying the modulo operator for divisibility checks.

Take your time, try to solve it on your own. Don’t be afraid to make mistakes; that’s how we learn! If you get stuck, peek at the solution below (but try really hard first!).

Click to reveal solution (but try it yourself first!)
// You can add this to your ControlFlowDemo.java or create a new FizzBuzz.java file

// In ControlFlowDemo.java, inside main method:
        System.out.println("\n--- Mini-Challenge: FizzBuzz ---");

        for (int num = 1; num <= 100; num++) {
            if (num % 3 == 0 && num % 5 == 0) { // Check for both first!
                System.out.println("FizzBuzz");
            } else if (num % 3 == 0) {
                System.out.println("Fizz");
            } else if (num % 5 == 0) {
                System.out.println("Buzz");
            } else {
                System.out.println(num);
            }
        }

5. Common Pitfalls & Troubleshooting

As you start using control flow, you might encounter some common issues. Here’s how to spot and fix them:

  1. Infinite Loops:

    • Problem: Your while or do-while loop never stops running, freezing your program.
    • Cause: The condition for the loop to terminate never becomes false. Often, you forgot to increment/decrement a counter, or the logic updating the condition is flawed.
    • Example: int i = 0; while (i < 5) { System.out.println(i); } (Here, i never changes, so i < 5 is always true).
    • Fix: Always ensure that something inside your loop body will eventually change the loop’s condition to false. For for loops, this is handled in the increment/decrement part.
  2. Forgetting Curly Braces {}:

    • Problem: Your if statement or loop only executes the first line after its condition, even if you intended multiple lines to be part of the block.
    • Cause: If you omit curly braces, Java only considers the single statement immediately following the if, else, while, do, or for as part of that block.
    • Example:
      if (true)
          System.out.println("This runs.");
          System.out.println("This always runs, NOT part of the if!"); // Indentation is misleading
      
    • Fix: Always use curly braces {} for your if, else, switch, and loop blocks, even if they contain only one statement. It improves readability and prevents subtle bugs.
  3. Logical Errors in if-else if-else Order:

    • Problem: Your conditions are met, but the wrong block of code executes. (Like in the FizzBuzz hint!)
    • Cause: In an if-else if-else ladder, Java executes the first true condition it encounters and then skips the rest. If a more general condition comes before a more specific one, the specific one might never be reached.
    • Example (FizzBuzz mistake):
      if (num % 3 == 0) { // If num is 15, this is true, prints Fizz
          System.out.println("Fizz");
      } else if (num % 5 == 0) { // This will never be checked for 15
          System.out.println("Buzz");
      } else if (num % 3 == 0 && num % 5 == 0) { // This specific case is never reached for 15!
          System.out.println("FizzBuzz");
      }
      
    • Fix: Always arrange your if-else if-else conditions from most specific to most general. For FizzBuzz, num % 3 == 0 && num % 5 == 0 must come first.

6. Summary: What You’ve Achieved

Congratulations! You’ve successfully navigated the exciting world of Java control flow. Here’s a quick recap of what you’ve learned:

  • Conditional Statements:
    • if: Execute code if a condition is true.
    • if-else: Choose between two paths based on a condition.
    • if-else if-else: Handle multiple distinct conditions in sequence.
    • switch (Modern JDK 25): A clean way to choose actions based on discrete values, using the -> operator for concise logic and yield for returning values from blocks.
  • Looping Statements:
    • while: Repeat code as long as a condition is true (0 or more times).
    • do-while: Repeat code at least once, then continue as long as a condition is true (1 or more times).
    • for: Ideal for definite loops with clear initialization, condition, and increment/decrement.
    • break: Immediately exit a loop or switch statement.
    • continue: Skip the current iteration of a loop and proceed to the next.

These control flow statements are the backbone of any dynamic program. You now have the tools to make your Java applications intelligent and responsive!

What’s Next? In Chapter 4, we’ll build on this foundation by exploring how to group related data using Arrays and how to organize your code into reusable blocks with Methods. Get ready to further structure your programs and write even more powerful Java code!