Unnamed Patterns and Variables (JEP 443): Writing Intent-Clear Code
Preview Feature — Requires
--enable-previewat compile and runtime. The_identifier was reserved in Java 9; this JEP gives it formal semantics.
The Problem: Forced Naming of Unused Things
When pattern matching, you often care about only some components of a matched structure. But the syntax forces you to name everything:
record Point(int x, int y) {}
record ColoredPoint(Point point, String color) {}
record Box(ColoredPoint cp, int weight) {}
// You only care about the color — but must name everything else
if (obj instanceof Box(ColoredPoint(Point(int x, int y), String color), int weight)) {
System.out.println("Color: " + color);
// x, y, weight are declared but never used → IDE warnings
}
Similarly in catch blocks:
try {
riskyOperation();
} catch (IOException e) {
// e is never used — just logging a fixed message
log.error("IO failed");
}
The variable e is a noise declaration: it exists only because the syntax requires it, not because you need it.
The Unnamed Pattern: _
In Java 21 with preview enabled, _ (single underscore) is the unnamed pattern — it matches any value and binds nothing:
// Only care about color — discard x, y, weight with _
if (obj instanceof Box(ColoredPoint(Point(_, _), String color), _)) {
System.out.println("Color: " + color);
}
Multiple _ in the same pattern are independent — each discards one component. They don’t conflict with each other.
Unnamed Variables in catch Blocks
try {
riskyOperation();
} catch (IOException _) { // unnamed — won't use the exception
log.error("IO operation failed");
}
// Multiple catches — all unnamed if not needed
try {
parseAndProcess(input);
} catch (NumberFormatException _) {
log.warn("Bad number format, using default");
return DEFAULT_VALUE;
} catch (ParseException _) {
log.warn("Parse failed, using default");
return DEFAULT_VALUE;
}
Unnamed Variables in Enhanced for Loops
When iterating a collection purely for its side effects (count, accumulate externally):
int count = 0;
for (var _ : items) {
count++;
}
// Or just to trigger side effects N times
for (var _ : requests) {
processNextInQueue();
}
Unnamed Variables in Assignment
// Call a method for its side effects, discard the return value explicitly
var _ = map.put("key", value); // signaling: return value intentionally ignored
// Common in multi-assignment scenarios
var (result, _) = twoValueReturn(); // hypothetical — shows intent
Full Pattern Matching Example with _
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height, String color) implements Shape {}
// Only interested in area — ignore color for Triangle
static double area(Shape shape) {
return switch (shape) {
case Circle(double r) -> Math.PI * r * r;
case Rectangle(double w, double h) -> w * h;
case Triangle(double b, double h, _) -> 0.5 * b * h; // color discarded
};
}
Combining with Type Patterns
_ works as an unnamed type pattern too — match any type without binding:
static boolean isInteresting(Object obj) {
return switch (obj) {
case String s when !s.isEmpty() -> true; // named — we use s
case Integer _ -> true; // unnamed — any Integer qualifies
case _ -> false; // unnamed default pattern
};
}
case _ is the unnamed default — equivalent to default but participates in exhaustiveness checking for sealed types.
Scope and Reuse Rules
_cannot be read — it has no name;System.out.println(_)is a compile error- Multiple
_in the same scope are independent — they don’t shadow each other _as an identifier (variable name with length ≥ 1) is still an error since Java 9:int _ = 5;is illegal;int _x = 5;is still legal
// Legal: multiple independent _ in one pattern
if (obj instanceof Box(ColoredPoint(Point(_, _), _), _)) { ... }
// Illegal: trying to read _
if (obj instanceof Box(ColoredPoint(Point(_, _), String color), _)) {
System.out.println(_); // COMPILE ERROR — _ has no name
}
Before and After
// BEFORE — noisy, forces naming of unused components
record OrderEvent(String orderId, String customerId, double amount,
String currency, Instant timestamp, String source) {}
if (event instanceof OrderEvent(String id, String cid, double amt,
String cur, Instant ts, String src)) {
// only use id and amt — all other vars declared but unused
processOrder(id, amt);
}
// AFTER — intent clear, compiler won't warn about unused vars
if (event instanceof OrderEvent(String id, _, double amt, _, _, _)) {
processOrder(id, amt);
}
Key Takeaways
_is the unnamed pattern — matches any value, binds nothing, creates no variable- Works in: record pattern components, type patterns,
instanceof,switch,catch, andfor-each - Multiple
_in one pattern are independent — each discards one position independently _cannot be read — it is a write-only discard, not a variable- The unnamed default pattern
case _participates in exhaustiveness checking unlikedefault - Requires
--enable-previewin Java 21; stabilization expected in a future release
Next: Unnamed Classes and Instance Main Methods (JEP 445) — write Java programs without class declarations for scripts, teaching, and quick experiments.