🚀 Modern Java 17 → 25 · Advanced

Unnamed variables & patterns in Java

Java 22 _ for unused variables and pattern components.

🧩 The mysterycatch (Exception ignored), for (var unused : list), (key, value) -> value... Names invented just to never be used. Java 22 gave "don't care" its own symbol: _.

Say "don't care"

Java 22 (JEP 456) made the underscore **_ official for variables you won't use: in catch blocks, lambda parameters, loops and more. It tells readers, and the compiler, that the value is intentionally unused**.

for (var _ : orders) { count++; }
map.forEach((_, v) -> System.out.println(v));
try { save(); }
catch (IOException _) { retryLater(); }

In patterns too

In a record pattern, _ matches a component without binding it: case Point(var x, _) says "I only care about x". It works when nested as well: case Box(Point(var x, _), _) ->.

🔮 Predict it

Your turn

What does this print?

record Pair(String a, String b) {}
void main() {
    Object o = new Pair("left", "right");
    if (o instanceof Pair(_, var b)) {
        System.out.println(b);
    }
}
  1. right
  2. left
  3. Compile error
Show the answer

right. The _ matches the first component without binding it, and var b binds the second one.

🔮 Predict it

Can you read it?

An unnamed variable gets a value. What happens when you print it?

int _ = 5;
System.out.println(_);
  1. 5
  2. 0
  3. Compile error
Show the answer

Compile error. _ never introduces a name, so there's nothing to refer to. You can **never read from _**: that's the whole promise.

As many as you like

Because _ never declares a name, it can't clash: **several _ in the same scope are fine**. That's what makes (_, _) -> 0 legal, where two named parameters would need two different names.

Naming the unused

✗ Fake name
try {
    n = Integer.parseInt(s);
} catch (NumberFormatException ignored) {
    n = -1;
}

"ignored" is still a real variable: someone can read it later, and readers must check whether anyone does.

✓ Unnamed
try {
    n = Integer.parseInt(s);
} catch (NumberFormatException _) {
    n = -1;
}

Intent is explicit, and the compiler stops anyone from reading it.

💼 In the real world

Intent, not speed

_ is about clarity and safety, not performance: it doesn't make code faster or free memory sooner, and nothing forces you to use it. Its payoff is in reviews: unused-variable warnings disappear, and readers instantly see which values were dropped on purpose.

Key takeaways

  1. catch (NumberFormatException _) { ... }
  2. case Box(Point(var x, _), _) ignores components
  3. Many _ per scope are fine; _ can never be read
  4. Final since Java 22 (JEP 456)
🤯 Did you know?

_ has a twisty history: Java 8 warned when you used it as a name, Java 9 made that an error, and Java 22 brought it back meaning "unnamed".

Practice questions

What does this print?

record Point(int x, int y) {}
void main() {
    Object o = new Point(5, 8);
    if (o instanceof Point(var x, _)) {
        System.out.println(x);
    }
}
  1. 5
  2. 8
  3. Point[x=5, y=8]
  4. Compile error
Check your answer

5. _ matches the y component without binding it, so only x (5) is available.

What does this print?

String s = "12x";
int n;
try {
    n = Integer.parseInt(s);
} catch (NumberFormatException _) {
    n = -1;
}
System.out.println(n);
  1. -1
  2. 12
  3. Throws NumberFormatException
  4. Compile error
Check your answer

-1. "12x" isn't a valid int, so the catch block runs. Naming the exception _ documents that its details aren't needed.

Next: Java 25 finally lets you run code BEFORE super(...) in a constructor. Validate first, then build.