break, continue and Labels

Stop a loop early with break, skip to the next pass with continue, and use labels to leave or skip an outer loop from inside a nested one.

Syntaxbreak; // leave the innermost loop or switch continue; // start the next pass outer: for (...) { for (...) { break outer; } }

break ends the innermost loop (or switch statement) immediately; execution continues after it. continue ends the current pass and starts the next one; in a for loop the update part (i++) still runs.

Typical uses

  • break once you have found what you were looking for, or when you hit an end marker or a limit, so you do not scan the rest of a large result.
  • continue to skip rows you cannot use (blank lines, comments, invalid records) at the top of the loop, which keeps the main logic unindented. It is the loop version of a guard clause.

Labels for nested loops

A plain break inside a nested loop only leaves the inner loop. The outer loop carries on, which is a common source of "found it, but then overwrote it" bugs. Put a label before the outer loop and name it:

search:
for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
        if (found(r, c)) {
            break search;
        }
    }
}

continue outer; works the same way: it abandons the inner loop and starts the next pass of the labelled loop. That is handy when one bad line should reject a whole order.

break inside switch

In the old colon-style switch, break leaves the switch, not the surrounding loop. A case "stop": break; inside a loop therefore does nothing to the loop. Use a labelled break to leave the loop. With arrow cases you do not need break to stop fall-through, but a labelled break still works inside a switch statement. Inside a switch expression you cannot use break or continue to jump out of the expression at all; produce the value with yield instead.

Prefer a method and return

Labels are legitimate Java, not a goto: they can only name an enclosing statement, and you can only break out of or continue it. Still, a deeply nested loop with labels is usually easier to read as a small method that returns the answer as soon as it finds it. For a single loop, a stream with filter and findFirst often says the same thing more directly.

Mistakes

  • Putting the i++ of a while loop after a continue: the update is skipped and the loop never ends.
  • Collecting a value and then breaking only the inner loop, so the outer loop replaces it.
  • Forgetting that statements after break in the same block are unreachable; the compiler rejects them.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Skip blank, comment and invalid rows in a CSV import while continuing with the rest.
  • Stop scanning a large result set as soon as a matching record is found.
  • Reject a whole order when any one of its lines fails validation.

More examples

Labelled break out of a nested search

The plain break stops each inner loop at its first free slot, but the outer loop keeps going and overwrites the answer with the last room's. The labelled break stops at the first free slot overall.

Example · java
void main() {
    String[][] rooms = {
        {"busy", "busy", "busy"},
        {"busy", "free", "free"},
        {"free", "busy", "busy"}
    };

    int room = -1, slot = -1;
    for (int r = 0; r < rooms.length; r++) {
        for (int s = 0; s < rooms[r].length; s++) {
            if (rooms[r][s].equals("free")) {
                room = r;
                slot = s;
                break; // leaves only the inner loop
            }
        }
    }
    IO.println("plain break:    room " + room + ", slot " + slot);

    room = -1;
    slot = -1;
    search:
    for (int r = 0; r < rooms.length; r++) {
        for (int s = 0; s < rooms[r].length; s++) {
            if (rooms[r][s].equals("free")) {
                room = r;
                slot = s;
                break search; // leaves both loops
            }
        }
    }
    IO.println("labelled break: room " + room + ", slot " + slot);
}
// Output:
// plain break:    room 2, slot 0
// labelled break: room 1, slot 1

Labelled continue rejects a whole order

continue orders abandons the inner loop and moves to the next order, so A-2 is never accepted. A plain continue would only skip the bad line and A-2 would be accepted.

Example · java
record Order(String id, List<Integer> quantities) {}

void main() {
    List<Order> orders = List.of(
        new Order("A-1", List.of(1, 2)),
        new Order("A-2", List.of(3, 0, 5)),
        new Order("A-3", List.of(4)));

    orders:
    for (Order order : orders) {
        for (int qty : order.quantities()) {
            if (qty <= 0) {
                IO.println("rejecting " + order.id() + ": quantity " + qty);
                continue orders; // skip the rest of this order's lines
            }
        }
        IO.println("accepting " + order.id());
    }
}
// Output:
// accepting A-1
// rejecting A-2: quantity 0
// accepting A-3

break inside a switch inside a loop

In the first loop the break only ends the switch, so seed still runs after stop. Naming the loop makes the intent explicit and actually stops processing.

Example · java
void main() {
    List<String> commands = List.of("migrate", "stop", "seed");

    for (String cmd : commands) {
        switch (cmd) {
            case "stop":
                IO.println("stop seen");
                break; // leaves the switch, the loop continues
            default:
                IO.println("run " + cmd);
        }
    }

    IO.println("--");

    loop:
    for (String cmd : commands) {
        switch (cmd) {
            case "stop" -> {
                IO.println("stopping");
                break loop; // leaves the loop
            }
            default -> IO.println("run " + cmd);
        }
    }
}
// Output:
// run migrate
// stop seen
// run seed
// --
// run migrate
// stopping

Discussion

  • Be the first to comment on this lesson.