The loops from lesson 02-02 are obedient: they start, run all the planned passes and finish. But real work is almost never that disciplined. When you are looking for the first loan with a maximum fine, there is no point in carrying on through the next thousand once you have found it. When you are going through pending returns and you hit one that has already been paid, you want to skip it without running the rest of the body. For those two needs —leaving early and skipping an iteration— Java offers break and continue. They are sharp tools: used well they make code faster and clearer; used badly they turn a loop into a maze. This lesson covers both, their interaction with switch (which hides the language's most classic trap), labels for breaking out of nested loops and the more readable alternatives to labels.
Contents
break: abandoning the loopcontinue: jumping to the next iteration- Both on the same loop: a side-by-side diagram
- The classic trap:
breakinside aswitchinside a loop - Labels:
break labelandcontinue label - More readable alternatives to labels
returninside a loop inmain- Comparison table:
break,continue,returnand a flag - A complete case: going through BiblioTech's pending returns
- Common Mistakes and Tips
- Exercises
break: abandoning the loop
break: abandoning the loopbreak immediately terminates the innermost loop containing it. Execution continues at the first statement after the loop; the rest of the body is not run, the condition is not evaluated, and the update is not performed.
final double DAILY_RATE = 0.25;
final double MAX_FINE = 20.0;
for (int days = 1; days <= 200; days++) {
double fine = days * DAILY_RATE;
if (fine >= MAX_FINE) {
System.out.printf("The cap is reached after %d days late.%n", days);
break; // no point in carrying on to 200
}
}
System.out.println("Search finished.");Output:
Without the break, the loop would make 200 passes and find the cap 121 times. With it, it stops at 80. This is the search with early exit pattern, and it is break's main reason for existing: as soon as you have the answer, stop working.
Compare it with the alternative from lesson 02-02, the boolean flag:
// With a flag: it keeps iterating even after it has found the answer
boolean found = false;
int capDays = 0;
for (int days = 1; days <= 200; days++) {
if (!found && days * DAILY_RATE >= MAX_FINE) {
capDays = days;
found = true;
}
}It works, but it makes 200 iterations and adds an extra condition to each one. When early exit is correct, break is clearer and more efficient.
continue: jumping to the next iteration
continue: jumping to the next iterationcontinue does not leave the loop: it abandons the current iteration and jumps to the next cycle. And here there is a nuance you must keep firmly in mind:
- In a
for,continuejumps to the update (i++) and then to the condition. - In a
whileordo-while, it jumps straight to the condition. If the update was in the body, after thecontinue, it does not run: guaranteed infinite loop.
// Go through the first 10 days and report only those producing a "round" fine
final double DAILY_RATE = 0.25;
for (int days = 1; days <= 10; days++) {
double fine = days * DAILY_RATE;
if (fine != Math.floor(fine)) {
continue; // not a whole amount: discarded
}
System.out.printf("%2d days -> %.2f EUR (whole amount)%n", days, fine);
}Output:
And the danger with while:
// INFINITE LOOP: the continue skips the i++
int i = 0;
while (i < 10) {
if (i == 3) {
continue; // back to the condition with i stuck at 3 forever
}
System.out.println(i);
i++;
}The fix is to update before the continue, or —better— use a for, where the update lives in the header and is never skipped.
continue is the filtering pattern: discard the elements you are not interested in and keep the main path free of extra indentation. In fact it is the "inside the loop" version of the guard clause you saw in lesson 02-01:
// With a nested if: the real work ends up indented
for (int n = 1; n <= 10; n++) {
if (isValid(n)) {
// ... twenty lines of work in here
}
}
// With continue: the real work stays flat
for (int n = 1; n <= 10; n++) {
if (!isValid(n)) {
continue;
}
// ... twenty lines of work, with no extra indentation
}
- Both on the same loop: a side-by-side diagram
The essential difference in a single drawing. The loop is the same; only the destination of the arrow changes:
flowchart TD
INI["Initialisation"] --> COND{"Loop condition"}
COND -- "false" --> FIN["Statement after the loop"]
COND -- "true" --> A["First part of the body"]
A --> Q{"Internal check"}
Q -- "continue" --> UPD["Update"]
Q -- "break" --> FIN
Q -- "carry on" --> B["Rest of the body"]
B --> UPD
UPD --> COND
Read it like this:
continuecuts towards the update: the loop lives, the iteration dies.breakcuts out of the loop: the loop dies, and with it every remaining iteration.
- The classic trap:
break inside a switch inside a loop
break inside a switch inside a loopThis is, without exaggeration, one of the three or four most famous traps in Java, and it produces bugs that cost entire afternoons. The rule is simple and merciless: break affects the innermost block containing it, and a switch counts as an inner block.
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("Option (1=browse, 0=exit): ");
String option = scanner.nextLine().trim();
switch (option) {
case "1":
System.out.println("Browsing the catalog...");
break; // leaves the switch. Correct.
case "0":
System.out.println("Exiting...");
break; // leaves the SWITCH, not the WHILE!
default:
System.out.println("Invalid option.");
}
}The programmer wrote break in case "0" believing it ended the program. What it does is leave the switch and go back to the top of the while (true): the menu is impossible to leave. The user picks 0, sees "Exiting…" and the menu reappears. Forever.
There are three correct ways to solve it:
Solution 1: a boolean flag (the most readable, and the one BiblioTech uses).
boolean exit = false;
while (!exit) {
String option = scanner.nextLine().trim();
switch (option) {
case "1":
System.out.println("Browsing the catalog...");
break;
case "0":
System.out.println("Exiting...");
exit = true; // the while condition takes care of it
break;
default:
System.out.println("Invalid option.");
}
}Solution 2: the arrow switch, where nothing invites you to write break because the branches no longer need it to separate themselves. It is worth being precise, because this is a nuance almost nobody explains well: if you insist and write case "0" -> { System.out.println("Exiting..."); break; }, that compiles and falls into exactly the same trap (it leaves the switch, not the loop). What does not compile is a bare case "0" -> break;, nor a break inside an expression switch. The arrow syntax does not protect you from the trap: it simply makes it unlikely to occur to you.
boolean exit = false;
while (!exit) {
String option = scanner.nextLine().trim();
switch (option) {
case "1" -> System.out.println("Browsing the catalog...");
case "0" -> {
System.out.println("Exiting...");
exit = true;
}
default -> System.out.println("Invalid option.");
}
}Solution 3: a label on the loop, which is the topic of the next section.
menuLoop:
while (true) {
String option = scanner.nextLine().trim();
switch (option) {
case "1":
System.out.println("Browsing the catalog...");
break;
case "0":
System.out.println("Exiting...");
break menuLoop; // breaks the LOOP, not the switch
default:
System.out.println("Invalid option.");
}
}A note about continue: continue has no meaning of its own inside a switch. If you write it there, it skips the iteration of the loop surrounding the switch, which is usually what you want; there is no ambiguity because a switch is not a loop. The trap is exclusive to break.
- Labels:
break label and continue label
break label and continue labelA label is an identifier followed by a colon, placed immediately before a loop. It lets break and continue act on an outer loop, not just on the innermost one.
labelName:
for (...) {
for (...) {
break labelName; // leaves the OUTER loop
continue labelName; // moves to the next iteration of the OUTER one
}
}Without a label, a break in a nested loop only abandons the inner one and the outer one carries on as if nothing had happened. A realistic BiblioTech example: searching, across several employees and their books, for the first return that has reached the maximum fine, and stopping the whole search right there.
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final double DAILY_RATE = 0.25;
final double MAX_FINE = 20.0;
String foundEmployee = "-";
int foundBook = 0;
int foundDays = 0;
// Simulated register: three employees, four books each.
// The days late are generated with a formula so that the test can be
// repeated identically (we cannot store a real list yet).
search:
for (int employee = 1; employee <= 3; employee++) {
for (int book = 1; book <= 4; book++) {
int daysLate = (employee * 29 + book * 17) % 130;
double fine = daysLate * DAILY_RATE;
System.out.printf("Employee %d, book %d: %3d days -> %6.2f EUR%n",
employee, book, daysLate, fine);
if (fine >= MAX_FINE) {
foundEmployee = "Employee " + employee;
foundBook = book;
foundDays = daysLate;
break search; // abandons BOTH loops
}
}
}
System.out.println();
if (foundDays > 0) {
System.out.printf("First capped fine: %s, book %d (%d days).%n",
foundEmployee, foundBook, foundDays);
} else {
System.out.println("No book has reached the maximum fine.");
}
}
}Output:
Employee 1, book 1: 46 days -> 11.50 EUR Employee 1, book 2: 63 days -> 15.75 EUR Employee 1, book 3: 80 days -> 20.00 EUR First capped fine: Employee 1, book 3 (80 days).
If instead of break search; you had written break;, the inner loop would have been abandoned, but the outer one would have moved on to employee 2 and the search would have continued, finding more results than you wanted.
continue label is less frequent, but it has its place: "this row no longer interests me, move on to the next one".
// If an employee has any book with a severe delay, we skip to the next
// employee without reviewing their remaining books.
perEmployee:
for (int employee = 1; employee <= 3; employee++) {
System.out.println("Reviewing employee " + employee);
for (int book = 1; book <= 4; book++) {
int daysLate = (employee * 29 + book * 17) % 130;
if (daysLate > 60) {
System.out.println(" Severe delay detected. The employee is flagged.");
continue perEmployee; // skip to the next employee
}
System.out.println(" Book " + book + " fine (" + daysLate + " days)");
}
System.out.println(" Employee with no incidents.");
}The obligatory warning. Labels are legal and sometimes they are the most direct solution, but abusing them is a design code smell. A method with three labels and criss-crossing jumps is as hard to follow as the goto that structured languages set out to eliminate. Practical rule of the trade: if you need a label, first ask yourself whether the inner loop should not be a separate method. Almost always the answer is yes.
- More readable alternatives to labels
Alternative 1: a boolean flag in the outer loop's condition.
boolean found = false;
for (int employee = 1; employee <= 3 && !found; employee++) {
for (int book = 1; book <= 4 && !found; book++) {
if (/* stopping condition */) {
found = true;
}
}
}Advantage: there are no jumps; the exit condition is visible in the header. Drawback: the flag appears in three places and you have to remember to update it. For two levels it is perfectly readable; for three, no longer.
Alternative 2: extract the inner loop into a method. When the inner loop lives in its own method, a simple return abandons it completely and the outer loop decides what to do with the result. It is the preferred solution in professional code, because it also gives a name to what that loop does. You cannot apply it yet —methods are studied in lesson 03-03— but keep it in mind: when you get there, most of your labels will disappear.
Alternative 3: rethink the loop. Many nested searches can be expressed as a single loop over a combined index, or they simplify radically once you have collections (module 5) and, later, the streams API (lesson 10-04), where "find the first one that satisfies X" is a single line.
return inside a loop in main
return inside a loop in mainreturn ends the method it appears in. Since in this module all your code lives in main, a return inside a loop ends the program, skipping everything else.
for (int days = 1; days <= 200; days++) {
if (days * DAILY_RATE >= MAX_FINE) {
System.out.println("Cap reached after " + days + " days.");
return; // ends main: the program finishes here
}
}
System.out.println("This line is NOT executed if the return happened.");Beware of one not-so-obvious consequence: if you have open resources (such as the Scanner), a premature return skips the scanner.close(). In a small console program it is not serious —the JVM releases everything on exit— but it is a bad habit that in module 6 you will fix for good with try-with-resources.
Inside main, return and a break from an outer loop look equivalent, but they are not: break lets the program carry on with whatever comes after the loop (the summary, closing the Scanner, the farewell); return kills everything. Use break to leave a loop and reserve return for genuinely ending the program.
- Comparison table:
break, continue, return and a flag
break, continue, return and a flag| Mechanism | What it abandons | What runs afterwards | Typical use | Risk |
|---|---|---|---|---|
break |
The innermost loop (or switch) |
The statement after the loop | Search with early exit | Confusing the switch with the loop |
break label |
The labelled loop, with everything nested | The statement after that loop | Searching in nested loops | Unreadable if abused |
continue |
The current iteration | The update and the condition | Filtering and discarding elements | Infinite loop in a while |
continue label |
The current iteration of the labelled loop | The labelled loop's update | Discarding a whole "row" | Very infrequent; surprises the reader |
return |
The whole method (here, main) |
Nothing else from the method | Ending the program | It skips the shutdown code |
| Boolean flag | Nothing: the loop ends by its condition | The rest of the body (unless guarded) | Menus, controlled exits | Verbose; it must always be updated |
- A complete case: going through BiblioTech's pending returns
We bring it all together in the example that gives the lesson its point. Nexus Software wants to review the queue of pending returns with two rules:
- Returns already paid are ignored (they do not count towards the totals).
- As soon as a return with a maximum fine appears, the review stops and the case is escalated to Human Resources.
Since you still cannot store a list, the register is simulated with two deterministic formulas, so that the output is always the same and you can check it. This is the equivalent content of the simulated register:
| No. | daysLate = (n * 37) % 130 |
paid = n % 2 == 0 |
Fine (with a €20.00 cap) |
|---|---|---|---|
| 1 | 37 | no | €9.25 |
| 2 | 74 | yes | — (paid) |
| 3 | 111 | no | €20.00 (capped) |
| 4 | 18 | yes | — (paid) |
| 5 | 55 | no | €13.75 |
| 6 | 92 | yes | — (paid) |
| 7 | 129 | no | €20.00 (capped) |
| 8 | 36 | yes | — (paid) |
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final String COMPANY_NAME = "Nexus Software";
final double DAILY_RATE = 0.25;
final double MAX_FINE = 20.0;
final int MINOR_THRESHOLD = 7;
final int TOTAL_PENDING = 8;
// Review accumulators
int reviewed = 0;
int skippedAsPaid = 0;
double outstandingTotal = 0.0;
boolean escalated = false;
int escalatedReturn = 0;
System.out.println("=== REVIEW OF PENDING RETURNS ===");
System.out.println("Company: " + COMPANY_NAME);
System.out.println();
for (int n = 1; n <= TOTAL_PENDING; n++) {
// --- Simulated register data ---
int daysLate = (n * 37) % 130;
boolean paid = (n % 2 == 0);
// --- CONTINUE: filter out what does not interest us ---
if (paid) {
skippedAsPaid++;
System.out.printf(" #%d ... already paid, skipped%n", n);
continue; // jump to the next n, without touching the totals
}
// From here on we know the return is outstanding.
double fine = daysLate * DAILY_RATE;
boolean capped = fine >= MAX_FINE;
if (capped) {
fine = MAX_FINE;
}
String severity;
if (daysLate == 0) {
severity = "ON TIME";
} else if (daysLate <= MINOR_THRESHOLD) {
severity = "MINOR";
} else {
severity = "SEVERE";
}
reviewed++;
outstandingTotal += fine;
System.out.printf(" #%d %3d days %-11s %6.2f EUR%s%n",
n, daysLate, severity, fine,
capped ? " <-- CAP" : "");
// --- BREAK: stop as soon as the escalation condition holds ---
if (capped) {
escalated = true;
escalatedReturn = n;
System.out.println(" Maximum fine reached: the review stops.");
break; // abandons the loop completely
}
}
System.out.println();
System.out.println("=== SUMMARY ===");
System.out.printf("%-28s %d of %d%n", "Returns reviewed:", reviewed,
TOTAL_PENDING);
System.out.printf("%-28s %d%n", "Skipped as already paid:", skippedAsPaid);
System.out.printf("%-28s %.2f EUR%n", "Outstanding reviewed:", outstandingTotal);
if (escalated) {
System.out.printf("%-28s yes (return #%d)%n", "Escalated to HR:",
escalatedReturn);
System.out.println("WARNING: some returns were left unreviewed after the stop.");
} else {
System.out.printf("%-28s no%n", "Escalated to HR:");
}
}
}Output:
=== REVIEW OF PENDING RETURNS ===
Company: Nexus Software
#1 37 days SEVERE 9.25 EUR
#2 ... already paid, skipped
#3 111 days SEVERE 20.00 EUR <-- CAP
Maximum fine reached: the review stops.
=== SUMMARY ===
Returns reviewed: 2 of 8
Skipped as already paid: 1
Outstanding reviewed: 29.25 EUR
Escalated to HR: yes (return #3)
WARNING: some returns were left unreviewed after the stop.Analyse what happened, because it condenses the whole lesson:
- Return #2 triggered the
continue: it was counted as skipped and did not enter the accumulators. The loop carried on. - Return #3 triggered the
break: it was accounted for first (which is whyoutstandingTotalincludes it) and then the loop was abandoned. Returns #4 to #8 were never looked at. - The order within the body matters: if the
breakwere beforeoutstandingTotal += fine, the capped fine would not have been added. Always place the exits where they make sense according to what you need to account for. - And once again, the underlying limitation: when it ends only aggregate figures remain. There is no way to list which returns were reviewed, nor to resume the review where it stopped. That requires storing the data, and for that you need module 5's collections.
Common Mistakes and Tips
1. Believing that a break in a switch leaves the loop. It is the trap from section 4. If your menu "never closes", this is why.
2. continue in a while without having updated beforehand. Infinite loop. If you use continue, prefer for.
3. Unreachable code after break or continue.
if (fine >= MAX_FINE) {
break;
System.out.println("Never printed"); // ERROR: unreachable statement
}Java detects it at compile time, which is a help.
4. break in the wrong place inside the body. Exiting before accumulating or after printing changes the result. Decide consciously: does the iteration that triggers the exit count or not?
5. Badly placed labels. The label must go immediately before the loop it refers to. If there is a statement in between, it does not compile.
6. Abusing labels. More than one in the same method usually means a method decomposition is missing. It is legal, it is not good.
7. Confusing return with break in main. return kills the whole program and skips the final summary and closing the Scanner.
8. Tip: one single exit point per loop, if you can. A loop with four breaks scattered around is hard to reason about. If that happens, the stopping condition probably deserves to be a named boolean variable.
9. Tip: continue at the top of the body, break where it belongs. The idiomatic pattern is to group all the filtering continues in the first lines of the body, as guard clauses, and leave the real work flat underneath.
10. Tip: comment the reason, not the mechanics. break; // leaves the loop adds nothing. break; // we have already found the first one, no need to continue does.
Exercises
Exercise 1: first day that exceeds a budget
Nexus Software allocates a €50.00 fine budget per employee per quarter. Write a program that goes through the days late from 1 to 500 accumulating the uncapped fine (days * DAILY_RATE, added day by day) and that stops with break as soon as the accumulated amount exceeds the budget. It must print on which day it happened and what the exact accumulated amount was at that moment. Also add a counter of iterations actually executed, to check that the break saved work.
Exercise 2: filtering with continue and spotting the while fault
Write two versions of the same program, going through the loan reference numbers from 1 to 20 and printing only those that are not multiples of 3 (multiples of 3 correspond to loans already archived and are discarded with continue):
- With a
forloop. - With a
whileloop.
In the while version, deliberately place the increment after the continue to reproduce the infinite loop, watch it (interrupt it with Ctrl + C) and then fix it. Explain in writing why the for does not suffer from that problem.
Exercise 3: menu with a classic switch and a correct exit
Write a looping menu with these options, using a classic switch (with : and break), not the arrow form:
The challenge is that the program actually ends when 0 is chosen. Implement it in two different ways and compare them:
- Version A: with a boolean flag in the
whilecondition. - Version B: with a label and
break label.
Add a counter of operations performed that is shown on exit, and check that it prints in both versions (hint: if you use return instead of break, it will not print).
Solutions
Solution 1
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final double DAILY_RATE = 0.25;
final double BUDGET = 50.0;
final int MAX_DAYS = 500;
double accumulated = 0.0;
int cutoffDay = 0;
int iterations = 0;
for (int days = 1; days <= MAX_DAYS; days++) {
iterations++;
accumulated += days * DAILY_RATE;
if (accumulated > BUDGET) {
cutoffDay = days;
break; // we have the answer: no need to keep adding
}
}
System.out.println("=== FINE BUDGET CONTROL ===");
System.out.printf("%-28s %.2f EUR%n", "Budget:", BUDGET);
if (cutoffDay > 0) {
System.out.printf("%-28s day %d%n", "Exceeded on:", cutoffDay);
System.out.printf("%-28s %.2f EUR%n", "Accumulated at that point:", accumulated);
} else {
System.out.printf("%-28s never in %d days%n", "Exceeded on:", MAX_DAYS);
}
System.out.printf("%-28s %d of %d%n", "Iterations executed:",
iterations, MAX_DAYS);
System.out.printf("%-28s %d%n", "Iterations saved:",
MAX_DAYS - iterations);
}
}Output:
=== FINE BUDGET CONTROL === Budget: 50.00 EUR Exceeded on: day 20 Accumulated at that point: 52.50 EUR Iterations executed: 20 of 500 Iterations saved: 480
Manual check: the sum of 1 + 2 + … + 20 is 210; multiplied by 0.25 that gives €52.50, which does indeed exceed €50. With 19 days it would be 190 × 0.25 = €47.50, still below. The break has saved 96 % of the work, and that saving is the main argument for using it in searches.
Solution 2
for version:
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
int shown = 0;
int archived = 0;
System.out.println("Active loan references (1-20):");
for (int reference = 1; reference <= 20; reference++) {
if (reference % 3 == 0) {
archived++;
continue; // in a for, it jumps to reference++ and then to the condition
}
System.out.printf(" REF-%03d%n", reference);
shown++;
}
System.out.printf("Shown %d, archived %d.%n", shown, archived);
}
}Output (abbreviated): REF-001, REF-002, REF-004, REF-005, REF-007… and at the end Shown 14, archived 6.
while version, broken first:
int reference = 1;
while (reference <= 20) {
if (reference % 3 == 0) {
continue; // BUG: it skips the increment below
}
System.out.printf(" REF-%03d%n", reference);
reference++;
}When you run it, the output stops after REF-002 and the program hangs without printing anything else: reference is 3, the if condition is always true and the reference++ is never reached. Interrupt it with Ctrl + C.
Fixed while version: you have to increment before jumping, which forces you to work with a shifted starting value:
int reference = 0;
int shown = 0;
int archived = 0;
while (reference < 20) {
reference++; // the increment goes FIRST, it always runs
if (reference % 3 == 0) {
archived++;
continue; // now it is safe
}
System.out.printf(" REF-%03d%n", reference);
shown++;
}
System.out.printf("Shown %d, archived %d.%n", shown, archived);Why the for does not suffer from the problem: in a for, the update (reference++) is part of the header, not of the body. continue jumps to the end of the body, but the update runs after that, always, whatever way the iteration ended. In a while, the update is just another line of the body and continue can skip it. This is the practical reason behind the rule "if you are going to use continue, use for".
Solution 3
Version A: boolean flag.
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final double DAILY_RATE = 0.25;
final double MAX_FINE = 20.0;
Scanner scanner = new Scanner(System.in);
boolean exit = false;
int operations = 0;
while (!exit) {
System.out.println();
System.out.println("=== BIBLIOTECH (version A) ===");
System.out.println(" 1. Review pending returns");
System.out.println(" 2. Check the fine cap");
System.out.println(" 0. Exit");
System.out.print("Option: ");
String option = scanner.nextLine().trim();
switch (option) {
case "1":
System.out.println(" 3 returns pending review.");
operations++;
break; // leaves the switch: correct and sufficient
case "2":
int capDays = (int) Math.ceil(MAX_FINE / DAILY_RATE);
System.out.printf(" The %.2f EUR cap is reached after %d days.%n",
MAX_FINE, capDays);
operations++;
break;
case "0":
exit = true; // the while CONDITION closes the loop
break;
default:
System.out.println(" Option not recognised.");
}
}
System.out.printf("Session finished. Operations performed: %d%n", operations);
scanner.close();
}
}Version B: label.
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final double DAILY_RATE = 0.25;
final double MAX_FINE = 20.0;
Scanner scanner = new Scanner(System.in);
int operations = 0;
menuLoop: // label, right before the loop
while (true) {
System.out.println();
System.out.println("=== BIBLIOTECH (version B) ===");
System.out.println(" 1. Review pending returns");
System.out.println(" 2. Check the fine cap");
System.out.println(" 0. Exit");
System.out.print("Option: ");
String option = scanner.nextLine().trim();
switch (option) {
case "1":
System.out.println(" 3 returns pending review.");
operations++;
break; // leaves the switch only
case "2":
int capDays = (int) Math.ceil(MAX_FINE / DAILY_RATE);
System.out.printf(" The %.2f EUR cap is reached after %d days.%n",
MAX_FINE, capDays);
operations++;
break;
case "0":
break menuLoop; // breaks the labelled LOOP
default:
System.out.println(" Option not recognised.");
}
}
System.out.printf("Session finished. Operations performed: %d%n", operations);
scanner.close();
}
}An identical session in both versions:
Option: 2 The 20.00 EUR cap is reached after 80 days. Option: 5 Option not recognised. Option: 1 3 returns pending review. Option: 0 Session finished. Operations performed: 2
Comparison. Version A needs no labels and its exit condition is visible in the header (while (!exit)), which makes it easier to read for somebody new to the code; in exchange, the exit variable is an extra element to maintain. Version B is more direct —a single jump, no auxiliary variable— but it requires the reader to know about labels and, if the method grew with more loops, it would start to get confusing. For a menu, the course recommends version A, and that is the one used in the lesson 02-06 project.
And check the detail in the hint: if in the case "0" of either version you wrote return; instead of break menuLoop; or exit = true;, the program would end instantly and the line Session finished. Operations performed: 2 would never be printed, besides leaving the Scanner unclosed. That is exactly the difference between "leaving the loop" and "ending the program".
Conclusion
You now control the flow inside a loop. break abandons the loop as soon as you have the answer and turns an exhaustive search into an efficient one; continue discards the iterations you are not interested in and keeps the body flat, like a guard clause. You know the language's most famous trap —the break that leaves the switch and not the loop— and its three solutions: a flag, the arrow switch and a label. You know how to use break label and continue label to govern nested loops, and also why it pays to be suspicious of them and prefer flags or, as soon as you can write them, methods with return. And you are clear about the difference between leaving a loop and ending the program.
BiblioTechApp can now go through the queue of pending returns, skip those already paid, stop at the first one with a maximum fine and escalate the case, all with scalar accumulators and a single loop. The program does quite a few things, and that is where the next problem appears: when the result is not what you expected, rereading the code is no longer enough. There are too many variables changing over too many iterations.
The next lesson, Debugging and Execution Traces, gives you the tools to see what really happens inside your program: traces with well-placed System.out.println, the IDE debugger with breakpoints (including conditional ones, to stop only when daysLate > 30), the variables window, expression evaluation, how to read a stack trace and, above all, a systematic method for locating a bug instead of hunting for it blindly.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
- Console Input and Output
- Your First Complete Program: BiblioTech
Module 2: Control Flow
- Conditional Statements
- Loops
- Switch Statements
- Break and Continue
- Debugging and Execution Traces
- Project: The BiblioTech Interactive Menu
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- The Object Class: equals, hashCode and toString
Module 4: Advanced Object-Oriented Programming
- Interfaces
- Abstract Classes
- Inner Classes
- Anonymous Classes
- Lambda Expressions
- Functional Interfaces and Method References
- Enums and Records
Module 5: Data Structures and Collections
- Arrays
- The Collections Framework
- ArrayList
- LinkedList
- HashMap
- HashSet
- Queue and Deque
- Stack
- Sorting and Searching Collections
Module 6: Exception Handling
- Introduction to Exceptions
- The Try-Catch Block
- Throw and Throws
- Custom Exceptions
- The Finally Block
- Try-with-resources and AutoCloseable
- Error Handling Strategies and Logging
Module 7: File Input/Output
- Reading Files
- Writing Files
- File Streams
- BufferedReader and BufferedWriter
- Serialization
- The NIO.2 API: Path and Files
- Interchange Formats: CSV and Properties
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
- Concurrent Collections and Atomic Variables
- Asynchronous Tasks with CompletableFuture
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection
- The Modern HTTP Client
Module 10: Advanced Topics
- Generics
- Annotations
- Reflection
- Java 8 Features: Streams and Optional
- Dates and Times with java.time
- Java 9 and Beyond
- Memory, Garbage Collection and Performance
Module 11: Java Frameworks and Libraries
- Introduction to Java Frameworks
- Spring Framework
- Hibernate
- JUnit
- Maven
- Advanced Testing with Mockito
- Essential Ecosystem Libraries
