Java Labeled Statements
Use labels in Java to break or continue outer loops from inside nested loops.
A plain break exits the innermost loop and a plain continue skips one iteration of it. When you're inside nested loops and need to control the outer loop from the inner one, Java gives you labeled statements: a name you can attach to a loop, and a break or continue that targets it by that name.
This is the closest thing Java has to goto — and it's intentionally limited to loops (and switch).
Defining a label
A label is an identifier followed by a colon, placed immediately before a loop:
outer:
for (int i = 0; i < 5; i++) {
// ...
}The name (outer here) follows the same rules as any Java identifier. There's no requirement to call it outer — searchLoop, rows, anything legal works. Pick a name that says why the label is there.
Labeled break
break <label>; exits the loop tagged with that label, regardless of how deeply nested you are inside it:
int[][] grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int target = 5;
int foundRow = -1, foundCol = -1;
search:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == target) {
foundRow = r;
foundCol = c;
break search;
}
}
}
System.out.println("found at " + foundRow + "," + foundCol);Without the labeled break, you'd need a flag variable or restructure the code. With it, you exit both loops at once the instant the target is found.
Labeled continue
continue <label>; skips the rest of both the current inner iteration and continues with the next iteration of the labeled outer loop:
rowLoop:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] < 0) {
continue rowLoop; // skip the rest of this row
}
process(grid[r][c]);
}
}When the inner loop hits a negative value, the rest of that row is skipped and we move on to the next row.
Use them sparingly
Labels work, but they're easy to abuse. Whenever you reach for one, consider the alternatives first:
-
Extract a helper method. Inside a method, a plain
returnexits all loops at once and is usually clearer:static int[] find(int[][] grid, int target) { for (int r = 0; r < grid.length; r++) { for (int c = 0; c < grid[r].length; c++) { if (grid[r][c] == target) return new int[]{r, c}; } } return null; } -
Use a flag — slightly less clean but explicit and
goto-free:boolean found = false; for (int r = 0; !found && r < grid.length; r++) { for (int c = 0; c < grid[r].length; c++) { if (grid[r][c] == target) { found = true; break; } } } -
Use streams for filtering-style problems instead of nested loops.
Reach for labels when the alternatives genuinely hurt readability — typically two or three levels deep with a clear "abort the search" or "next outer iteration" intent.
Labels and switch
Labels work with switch too, though it's rare:
sw:
switch (cmd) {
case "x":
if (someCondition) break sw;
// ...
break;
}In practice this never adds clarity over a plain break.
A worked example
What's next
You've finished the control-flow chapters: conditionals, the ternary, switches, loops, and the statements that interrupt them. Up next is the part on methods — packaging blocks of code so you can call them by name.
Practice
What does a labeled break do?