Pattern Variable Scope (Flow Scoping)
A pattern variable is the variable a pattern declares, like s in o instanceof String s. Unlike an ordinary local variable, its scope isn't simply "the block it's written in". The compiler gives it a scope based on where the match is known to have succeeded. This rule is called flow scoping. In this lesson, you will learn exactly where a pattern variable can be used, with if, else, &&, ||, the ternary operator, early returns and loops, and the errors you get when you step outside that scope.
"Java Instanceof and Pattern Matching" introduced the basic idea; this lesson goes through every case in detail.
The Rule in One Sentence
A pattern variable is in scope wherever the compiler can prove the pattern matched, and nowhere else.
The compiler works this out from the code's structure, without running it:
| Code | Where s is in scope |
|---|---|
if (o instanceof String s) { A } else { B } | In A only |
if (!(o instanceof String s)) { A } else { B } | In B only |
o instanceof String s && X | In X |
o instanceof String s || X | Not in X; there the match failed |
!(o instanceof String s) || X | In X; X runs only if the match succeeded |
o instanceof String s ? A : B | In A only |
if (!(o instanceof String s)) return; followed by more code | In all the code after the if |
while (!(o instanceof String s)) { ... } with no break | In the code after the loop |
Example: Every Case in One Program
Let's see an example that uses a pattern variable in each of these positions:
public class Flow {
static String name = "field";
static int lengthOrZero(Object o) {
return o instanceof String s ? s.length() : 0;
}
static void elseBranch(Object o) {
if (!(o instanceof String s)) {
System.out.println("not a string");
} else {
System.out.println("else branch sees " + s);
}
}
static void loop(Object[] items) {
int i = 0;
Object o = items[i];
while (!(o instanceof String s)) {
o = items[++i];
}
System.out.println("first string: " + s);
}
static void shadow(Object o) {
if (o instanceof String name) {
System.out.println("inside: " + name);
}
System.out.println("outside: " + name);
}
static void reassign(Object o) {
if (o instanceof String s) {
s = s.toUpperCase();
System.out.println(s);
}
}
public static void main(String[] args) {
System.out.println(lengthOrZero("Navika"));
System.out.println(lengthOrZero(7));
elseBranch(5);
elseBranch("Advitha");
loop(new Object[] {1, 2.5, "Srihitha", "x"});
shadow("pattern");
reassign("srihitha");
}
}6
0
not a string
else branch sees Advitha
first string: Srihitha
inside: pattern
outside: field
SRIHITHAExplanation:
lengthOrZero: in the ternary,sexists only in the "true" part,s.length().elseBranch: the test is negated, so theifblock runs when the match failed and theelseblock when it succeeded.sis available in theelseblock.loop: the loop keeps going whileois not aString. When the loop ends, the match must have succeeded, sosis usable after the loop.shadow: the pattern variablenamehides the static fieldnameinside theif. After theif,namemeans the field again.reassign: a pattern variable is an ordinary variable, so you can assign to it, unless you declare itfinal.
The Early-Return Style
The most useful case in real code is rejecting the wrong type at the top of a method:
public class EarlyReturn {
record Student(String name, int marks) { }
static String report(Object o) {
if (!(o instanceof Student s)) {
return "not a student";
}
// From here on, s is a Student.
return s.name() + " scored " + s.marks();
}
public static void main(String[] args) {
System.out.println(report(new Student("Navika", 91)));
System.out.println(report("hello"));
}
}Navika scored 91
not a studentExplanation: If o isn't a Student, the method returns. So the code after the if only runs when the match succeeded, and s stays in scope for the rest of the method. This keeps the main logic unindented.
When the Variable Is Not in Scope
Using the variable where the match might have failed is a compile error:
static void m(Object o) {
if (o instanceof String s) {
System.out.println(s);
}
System.out.println(s.length());
}F3.java:6: error: cannot find symbol
System.out.println(s.length());
^
symbol: variable s
location: class F3
1 errorAfter the if, the match may have failed, so s doesn't exist. The same happens when a loop can be left with break, because then the loop can end without a match:
while (!(o instanceof String s)) {
if (o == null) break;
o = o.toString();
}
System.out.println(s); // error: cannot find symbolName Clashes and Final
A pattern variable may hide a field (as the shadow method showed), but it may not reuse the name of a local variable that is still in scope:
E1.java:5: error: variable s is already defined in method main(String[])
if (o instanceof String s) { }
^
1 errorTo stop a pattern variable from being reassigned, declare it final:
F2.java:4: error: cannot assign a value to final variable s
s = "x";
^
1 errorThat error comes from if (o instanceof final String s) { s = "x"; }.
Scope in Switch Cases
In a switch, each case's pattern variable is in scope only in that case's guard and body:
switch (o) {
case String s -> System.out.println(s.length()); // s is a String here
case Integer s -> System.out.println(s + 1); // a different s
default -> System.out.println("other");
}Both cases can use the name s, because the scopes don't overlap. "Pattern Matching for Switch in Java" covers this in detail.
Common Mistakes
Using the variable after an if that doesn't return. Either use it inside the if, or turn the test around and return early.
Combining with ||. In o instanceof String s || s.isEmpty(), the right side runs only when the match failed, so s isn't available.
Reusing a local variable's name. Pick a different name for the pattern variable.
Adding a break to a loop that defines a pattern variable. The variable is then no longer in scope after the loop.
Interview Corner
What is flow scoping in Java pattern matching?
Flow scoping means a pattern variable is in scope exactly where the compiler can prove the pattern matched. In if (o instanceof String s) it is in the if block; with a negated test it is in the else block, or after the if when the if block always exits (return or throw). It is available on the right of && but not of ||, in the true branch of a ternary, and after a while (!(o instanceof T t)) loop that has no break. A pattern variable can hide a field but can't redeclare a local variable, and it can be reassigned unless declared final.
Practice
- Easy: Rewrite a method that uses
if (o instanceof Student s) { ...long body... }into the early-return style. - Harder: For each row of the table at the top of this lesson, write a two-line method and check with the compiler whether
sis in scope where the table says.
Key Takeaways
Was this tutorial helpful?