An unnamed variable is written _ and says "something goes here and I will not use
it". It removes the invented names — ignored, unused, e2 —
that clutter code where a value is structurally required but genuinely irrelevant.
A note on versions
This was a preview feature in Java 21, not a standard one. On Java 21 the examples
below need --enable-preview; from Java 22 onwards they compile normally. The examples
here are compiled against Java 25, where they are standard.
# Java 21 only
javac --release 21 --enable-preview Example.java
java --enable-preview Example
# Java 22 and later
javac Example.java
Worth knowing which side of that line your build is on before adopting the syntax, because a preview feature also requires the flag at runtime and pins the class file to that exact release.
Unused loop variables
class Demo {
int countItems(List<String> items) {
int total = 0;
for (String _ : items) { // the element is irrelevant; we are counting
total++;
}
return total;
}
}
The limit is worth stating immediately: _ declares a variable you cannot refer
to. A classic for loop reads and updates its counter, so it cannot be unnamed —
this does not compile, on any version:
class Demo {
void repeat(int times) {
for (int _ = 0; _ < times; _++) { // error: underscore not allowed here
}
}
}
Only variables you genuinely never mention qualify.
Unused catch parameters
class Demo {
boolean isNumber(String text) {
try {
Integer.parseInt(text);
return true;
} catch (NumberFormatException _) { // the exception carries nothing we need
return false;
}
}
}
This is the most defensible use in the whole feature. The old version named a variable
e and never touched it, which looks exactly like the
swallowed exception that post warns about.
_ makes the difference visible: this is a deliberate decision that the exception carries
no information, not an oversight.
Unused lambda parameters
class Demo {
void run() {
Map<String, Integer> scores = new HashMap<>(Map.of("Ana", 30));
// Only the key matters
scores.forEach((name, _) -> System.out.println(name));
// Only the value matters
scores.forEach((_, score) -> System.out.println(score));
// computeIfAbsent hands you the key; sometimes you do not need it
scores.computeIfAbsent("Bo", _ -> 0);
}
}
Unnamed patterns
The other half of the feature, and the one that composes best with
record patterns. A _ in a pattern matches a
component without binding it:
record Point(int x, int y) { }
record Line(Point start, Point end) { }
class Demo {
String describe(Object value) {
return switch (value) {
// Only the x coordinates matter here
case Line(Point(int x1, _), Point(int x2, _)) when x1 == x2 -> "vertical";
case Line(Point(_, int y1), Point(_, int y2)) when y1 == y2 -> "horizontal";
case Line _ -> "diagonal"; // matched, contents irrelevant
default -> "not a line";
};
}
}
Without _, that first case reads Line(Point(int x1, int y1), Point(int x2, int
y2)) and declares two variables nobody uses. The version above says which two of the four
components the decision depends on, which is information the reader wants.
A type pattern can be unnamed too — case Line _ tests the type and binds nothing.
The rules
- You cannot read it. Referring to
_is a compile error, which is what makes the intent enforceable rather than a convention. - You can declare several in one scope. Two variables named
_do not clash, because neither can be referred to. This is the one place Java relaxes its no-duplicate-names rule. - It works for local variables, catch parameters, lambda parameters, patterns, and the resource variable in a try-with-resources.
- It does not work for fields, method parameters, or anything that forms part of a public signature.
class Demo {
void multiple(List<String> a, List<String> b) {
for (String _ : a) {
for (String _ : b) { // legal — neither can be referenced
System.out.println("pair");
}
}
}
// A resource you must close but never use
void resource(Path path) throws IOException {
try (var _ = Files.newBufferedReader(path)) {
System.out.println("opened and closed");
}
}
}
What it is not
Two things _ resembles in other languages and is not in Java.
It is not a wildcard you can assign to repeatedly. In some languages
_ is a sink you can write to as often as you like. In Java it is a declaration, and each
one declares a separate variable that happens to be unnameable — there is no shared bucket.
It is not a way to ignore a return value. Java has never required you to use a return value, so there is nothing to suppress:
class Demo {
void run(List<String> names) {
// var _ = names.size(); // legal, but pointless
names.size(); // already fine — Java does not warn on an unused result
}
}
The one place the assignment form earns its keep is try-with-resources, where the language requires a variable in order to close the resource and you have no use for it — the example above.
Why the underscore was available
A small piece of history that explains the odd delay. _ was a perfectly ordinary
identifier in early Java — people used it as a variable name. Java 8 made it a warning, Java 9 made
it a compile error, and that deliberate deprecation is what freed the character for this use a decade
later.
So if you meet _ as a real variable name, you are looking at code written for Java 7
or earlier, and it will not compile on anything modern.
Worth using?
It is a small feature and it does not change how you design anything. Two uses genuinely improve
code: the catch parameter, because it distinguishes a deliberate ignore from a swallowed
exception, and unnamed patterns, because they say which components a decision actually depends on.
The rest is tidiness. Use it where it removes a lie — a name suggesting a value matters when it does not — and do not go hunting for opportunities.
Next
Other Java 21 improvements is next.