Topic 11.4
Unnamed Variables and Patterns
In one line
From Java 22 you can write _ instead of a name for a variable or pattern you must declare but never use: catch (NumberFormatException _), (_, v) -> ..., case Point(var x, _). It tells readers and the compiler "this is deliberately ignored", and it can't be read by mistake.
Think of it like this
A form that asks for your middle name when you don't have one. You still have to fill the box, so people write "none" or "N/A". Java has the same problem: a catch must name its exception, a lambda must name all its parameters, a record pattern must list every component, even when you don't need them. _ is Java's official "N/A": a box filled in on purpose with nothing to read.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Unnamed variable
- A variable declared with the name
_. It must be declared (the syntax needs it) but can never be used. - Unnamed pattern variable
- A type pattern named
_, likeCircle _. It checks the type but keeps no variable. - Unnamed pattern
- A bare
_inside a record pattern, likePoint(var x, _). It matches any value in that position, even null, and binds nothing. - Keyword
- A word the Java language reserves for itself, like
classorif. A keyword can't be used as a name._has been one since Java 9. - Side effect
- Something a call does besides returning a value, like printing, removing from a queue or opening a file.
- Preview feature
- A finished-but-not-final language feature you can try with
--enable-preview. It may change before it becomes standard. - Lint warning
- A warning from the compiler (enabled with
-Xlint) about code that compiles but looks like a mistake.
Step by step
01The problem: names you never read
Java's syntax often makes you declare a variable you don't need. A catch clause needs a parameter. A BiConsumer lambda needs two parameters. An enhanced for needs a loop variable even if you only count. A record pattern needs one nested pattern per component.
Programmers invent names like ignored, unused or e for these. The compiler can't tell the difference between "I chose not to use this" and "I forgot to use this", and some tools warn about it: javac -Xlint flags an unused try-with-resources variable.
02Unnamed variables: _ where a declaration is required
In Java 22, replace each unused name with _. The program behaves exactly the same; only the intent is clearer. The declaration still happens (the loop still iterates, the resource is still closed, the lambda still receives two arguments), but no variable name is introduced.
Allowed places: local variables, enhanced-for variables, extra variables in a basic for header, catch parameters, try-with-resources resources, and lambda parameters.
// Java 22+
for (String _ : List.of("tea", "coffee", "milk")) {
count++; // only counting
}
try {
Integer.parseInt(s);
} catch (NumberFormatException _) { // the exception object isn't needed
System.out.println("not a number");
}
stock.forEach((_, qty) -> System.out.println("qty " + qty));
try (Timer _ = new Timer()) { // opened and closed for its side effects
System.out.println("doing work");
}
var _ = queue.poll(); // call for the side effect, drop the value03Unnamed patterns inside record patterns
Topic 11.3's record patterns must list every component. When a case only cares about one component, the rest used to be var unusedX. In Java 22 a bare _ stands for "anything here, I don't care": case Scroll(_, var dy).
The bare _ matches any value in that position, including null, just like var. It is only legal inside a record pattern. case _ -> on its own isn't valid Java; for a catch-all, use default.
// Java 22+
case Click(var x, var y, _) -> "click at " + x + "," + y; // button ignored
case Key(var ch, _) -> "key " + ch; // shift ignored
case Scroll(_, var dy) -> dy > 0 ? "scroll down" : "scroll up";04Unnamed pattern variables and several patterns per case
case Resize _ -> matches a Resize but doesn't bind it. That unlocks a new shape: one case with several patterns, case Scroll _, Resize _ -> "layout". Each pattern is tried in turn, and since none binds anything, the case body can't depend on which one matched.
If any of the patterns binds a variable, the compiler rejects the label: after a match, some of the variables would be unassigned.
// Java 22+
static String category(Event e) {
return switch (e) {
case Click _, Key _ -> "input";
case Scroll _, Resize _ -> "layout";
};
}05Where _ is not allowed
Fields, method parameters, constructor parameters and record components are part of a type's signature. Other code, documentation and reflection refer to them by name, so _ is rejected there. Implementing an interface method whose parameter you don't need? Keep a normal name.
_ can never be read. Writing System.out.println(_) fails. Here is JDK 21 compiling with the preview enabled; JDK 22 and later report the same rule:
06Running it: you need JDK 22 or newer
On JDK 21 the feature is a preview and is off by default, so _ is a compile error unless you pass --enable-preview. On JDK 22 and later it is standard. With the source-file launcher you can run it directly:
The output is identical to the runnable Java 17/21 versions in the examples below, because _ changes only names, not behaviour.
07Why _ took eight years
In Java 8 and earlier, _ was a legal identifier, and some code used it as a variable or method name. Java 8's javac warned about it, Java 9 turned it into a keyword so that no new code could use it, and only after that could Java 21 and 22 give _ a new meaning without changing what existing valid programs do.
Compiling old code that used _ as a name now fails on modern JDKs; the fix is to rename it.
Try it yourself
- 1
Spot the unused names
In the first runnable example, list every variable that is declared but never read. Compile it with
javac -Xlint:all Main.javaand see which one the compiler warns about. - 2
Run the Java 22 version locally
If you have JDK 22 or newer, save the unnamed-patterns example as
Main.javaand runjava Main.java. Then addSystem.out.println(category(e));inside the loop and predict the six new lines first. - 3
Try a binding in a multi-pattern case
In the Java 22 example, change
case Click _, Key _tocase Click c, Key _. Predict whether it compiles, then check the error.
Code & diagrams
Expected output
items: 3
parsed 42
'4x2' is not a number
qty 10
qty 4
timer started
doing work
timer stoppedRun with JDK 22 or newer (java Main.java). It prints exactly the same eight lines as the example above.
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Main {
static class Timer implements AutoCloseable {
Timer() { System.out.println("timer started"); }
@Override public void close() { System.out.println("timer stopped"); }
}
public static void main(String[] args) {
int count = 0;
for (String _ : List.of("tea", "coffee", "milk")) {
count++;
}
System.out.println("items: " + count);
for (String s : List.of("42", "4x2")) {
try {
System.out.println("parsed " + Integer.parseInt(s));
} catch (NumberFormatException _) {
System.out.println("'" + s + "' is not a number");
}
}
Map<String, Integer> stock = new LinkedHashMap<>();
stock.put("tea", 10);
stock.put("coffee", 4);
stock.forEach((_, qty) -> System.out.println("qty " + qty));
try (Timer _ = new Timer()) {
System.out.println("doing work");
}
}
}Expected output
click at 10,20
right click at 5,5
key A
key b
scroll up
window changedRun with JDK 22 or newer. Same output as the Java 21 version above.
public class Main {
sealed interface Event permits Click, Key, Scroll, Resize { }
record Click(int x, int y, int button) implements Event { }
record Key(char ch, boolean shift) implements Event { }
record Scroll(int dx, int dy) implements Event { }
record Resize(int w, int h) implements Event { }
static String handle(Event e) {
return switch (e) {
case Click(var x, var y, var button) when button == 2 -> "right click at " + x + "," + y;
case Click(var x, var y, _) -> "click at " + x + "," + y;
case Key(var ch, var shift) when shift -> "key " + Character.toUpperCase(ch);
case Key(var ch, _) -> "key " + ch;
case Scroll(_, var dy) -> dy > 0 ? "scroll down" : "scroll up";
case Resize _ -> "window changed";
};
}
static String category(Event e) {
return switch (e) {
case Click _, Key _ -> "input"; // several patterns, no bindings
case Scroll _, Resize _ -> "layout";
};
}
public static void main(String[] args) {
Event[] events = {new Click(10, 20, 1), new Click(5, 5, 2), new Key('a', true),
new Key('b', false), new Scroll(0, -3), new Resize(800, 600)};
for (Event e : events) {
System.out.println(handle(e));
}
}
}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use _ on a JDK that doesn't support it
Write catch (NumberFormatException _) and compile with JDK 21 (no preview flag).
Break #2
Read an unnamed variable
Declare int _ = 5; and then write System.out.println(_);.
Break #3
Bind a variable in a multi-pattern case
Write case Circle c, Square q -> "x"; in a switch over a sealed Shape.
Myth vs fact
Myth
_ is a normal variable name with a convention.
Fact
Since Java 9 it's a keyword. Since Java 22 it declares a variable with no name at all; you can't read it, and many _ can coexist in one scope.
Myth
case _ -> is a catch-all, like in other languages.
Fact
Not in Java. The bare _ pattern is only allowed nested inside a record pattern. Use default for a catch-all case.
Myth
Using _ skips the work, so a for (var _ : list) loop or catch (E _) is cheaper.
Fact
Nothing is skipped: the loop still iterates and assigns, the exception is still caught. Only the name is gone.
Myth
You can use _ for unused method parameters.
Fact
No. Method, constructor and record parameters are part of the signature and must be named. _ is for locals, catch parameters, resources, lambda parameters and patterns.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
At run time an unnamed variable behaves exactly like a named but unused one; javac may store the value in a local slot or simply discard it. Compiled with
-g, it produces no entry in theLocalVariableTabledebug info, so debuggers don't show it. - ▸
The unnamed pattern
_is unconditional for its component type, so it matchesnullcomponents, exactly likevar x. That differs from a nested type pattern such asString _on anObjectcomponent, which only matches non-null Strings. - ▸
Multi-pattern case labels (
case A _, B _) are part of the same JEP. They also let you merge cases in an exhaustive switch over a sealed type without falling back todefault, which preserves the compile error when a new subtype is added. - ▸
Static analysis tools and code reviewers can treat
_as a guarantee: a named but unused variable is a likely bug, an unnamed one is deliberate. Lint configurations can flag unused named variables more aggressively once a codebase moves to Java 22+.
Remember this
- 1
An unnamed variable is declared with
_and cannot be read or written after its declaration. You can use it for a local variable (var _ = queue.poll();), the variable of an enhancedforloop, an extra variable in a basicforheader, acatchparameter, a try-with-resources resource, and a lambda parameter. Several_can be declared in the same scope, because they never clash:(_, _) -> 0is fine. - 2
An unnamed pattern variable is a type pattern with
_as its name:case Circle _ ->matches anyCirclewithout binding it. The unnamed pattern is a bare_inside a record pattern, which matches any component value, includingnull, without binding it:case Point(var x, _). It works likevar unusedbut says what you mean, and it is only allowed nested inside a record pattern, never as a wholecaselabel on its own. - 3
With unnamed pattern variables, a
casemay list several patterns separated by commas:case Scroll _, Resize _ -> "layout". This is allowed only because none of them binds a variable; with bindings (case Circle c, Square s) the compiler couldn't know which variable is assigned, and reportsillegal fall-through from a pattern. - 4
_is not allowed for fields, method or constructor parameters, or record components: those are part of a class's API and must have names. And it can never be used as an expression:System.out.println(_)is a compile error. - 5
History: Java 8 warned when
_was used as an identifier; Java 9 made_a reserved keyword (an error as a name) to free it for this feature. Unnamed variables and patterns were a preview in Java 21 (JEP 443) and became standard in Java 22 (JEP 456). The runner on this page uses Java 17, so the_examples below are shown as code to run on JDK 22 or newer, next to runnable versions that use ordinary names.
Explain it without notes
Where can _ be used as an unnamed variable, and where can't it?
What is the difference between an unnamed pattern variable (Circle _) and the unnamed pattern (_)?
Why are multiple patterns in one case allowed only when none binds a variable?
Why did Java make _ a keyword in Java 9, years before this feature?
Practice
Using JDK 22+ syntax, rewrite for (int i = 0; i < 3; i++) { System.out.println("hi"); } as an enhanced for over List.of(1, 2, 3) with an unnamed loop variable, and say what it prints.
Given record Order(String id, String customer, int totalPaise), write a Java 22 switch that returns "big" when totalPaise > 100000 and "normal" otherwise, using _ for the two unused components. Then write the Java 21 equivalent and run it.
Write a Map<String, Integer> with three entries in a TreeMap and print only the values using forEach with an unnamed key parameter (Java 22). Give the Java 17 version too.
Trade-offs
- ↔
_makes intent explicit and prevents accidental reads, but code using it needs Java 22+. Libraries that support older JDKs must keep ordinary names. - ↔
Replacing record components with
_in patterns shortens cases, but too many_in a long record pattern (Order(_, _, _, var x, _)) is hard to read. If you only need one component, a type pattern plus an accessor (case Order o -> o.total()) may be clearer. - ↔
Swallowing exceptions with
catch (Exception _)is now one character shorter, which makes a bad habit easier. Ignoring an exception should still be rare, specific (NumberFormatException, notException) and deliberate.
Done when you can
Done when you can list the places
_may and may not appear.Done when you can rewrite a record pattern with unused components using
_.Done when you can explain unnamed pattern variables and multi-pattern case labels.
Done when you know which JDK versions accept
_and what older ones say.