What is JEP 456?
JEP 456 is a feature of Java 22 that finalizes the capability to use “unnamed variables” and “unnamed patterns”. In short, when you need a variable binder (in a local variable declaration, exception parameter, lambda parameter, loop header, or pattern matching) but you are never going to use that variable, you can now use the underscore character _ (U+005F) instead of naming the variable.
Specifically:
- An unnamed variable is one declared via
_instead of a named identifier. - An unnamed pattern variable is one declared via
_in a type-pattern or nested pattern, indicating the binding is present for the grammar but the name is deliberately omitted. - In addition, switches over patterns may now use multiple patterns in a single
caselabel, provided that none of the patterns declare any pattern variables (i.e., you can’t mix named pattern variables in a multi-patterncase).
The heading summary in the JEP says:
Enhance the Java programming language with unnamed variables and unnamed patterns, which can be used when variable declarations or nested patterns are required but never used. Both are denoted by the underscore character,
_.
Release: Java 22.
Scope: Standard Edition (SE) language.
Relation: This builds directly on the preview feature from JEP 443 (Unnamed Patterns & Variables) in JDK 21.
Why is JEP 456 useful? (Motivation)
Understanding why this was introduced helps you decide when to use it and what its benefits are.
Avoiding meaningless names
Often in code you must declare a variable, parameter, or pattern binding because the grammar demands it, but you then never use that binding. For example:
for (Order order : orders) { // order is unused
total++;
}
Here order is a declared variable, but not really used — it’s just the loop-variable needed to iterate. The name is syntactically required but semantically irrelevant.
If you could instead say:
for (Order _ : orders) { … }
you communicate explicitly “we are discarding the loop variable”. That improves readability and prevents accidental use.
Avoiding static‐analysis noise
When variable names are declared but unused, static analysis tools (and IDE warnings) often raise “unused variable” warnings. Using _ signals intent: “I don’t intend to use it.” The specification states:
The intent of non‐use is known at the time the code is written, but if it is not captured explicitly then later maintainers might accidentally use the variable, thereby violating the intent.
Thus this feature helps reduce unintentional uses of variables that were never meant to be used.
Cleaner pattern matching code
In the era of pattern matching (e.g., instanceof, switch on types, record patterns), you often only care about some of the components in a pattern, and other parts are unused. But you still must bind them if the grammar asks for it. E.g.:
if (r instanceof ColoredPoint(Point(int x, int y), Color c)) {
// you only use x and y, not c
}
Here c is unused. You can replace that with _ (unnamed pattern variable) to indicate it’s unused.
Also you may want to elide entire unused nested patterns – the unnamed pattern feature then lets you drop the type pattern for an unused component altogether.
More expressive multi‐pattern switch cases
Because switching on many patterns is becoming common, allowing multiple patterns in a single case label (provided none declare pattern variables) improves conciseness:
switch (box) {
case Box(RedBall _), Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
This reduces duplication when different patterns lead to the same action and none of them need to bind variables.
What the feature isn’t (Non-Goals)
It’s worth knowing the boundaries of this JEP:
- It is not a goal to allow unnamed fields or unnamed method parameters (i.e., class fields or formal parameters of methods cannot use
_for the name in Java 22). - It does not alter the semantics of local variables in the sense of definite‐assignment analysis, or other variable semantics.
Detailed Description & Syntax Changes
Here we walk through how to use the new features, where they apply, and with examples.
1. Unnamed Variables
You may now declare a local variable whose name is simply _ (underscore) when the variable is required but you don’t intend to use it. The scope contexts where this is allowed are:
- Local variable declaration statements (in a block)
- Resource specification in a
try-with-resourcesstatement - The header of a basic
forloop - The header of an enhanced
forloop - An exception parameter in a
catchclause - A formal parameter of a lambda expression
Key semantic points:
- Declaring
_as a variable does not introduce a name into the scope. So you cannot read or write to it later. E.g., you cannot write_ = …elsewhere (except its initializer) or use_as a variable. - For a local variable declaration of
_, an initializer must be provided (in places where a local variable must be initialized). - Because
_introduces no binding into the surrounding scope, it cannot shadow any other variable; you can even have multiple unnamed variables in the same block safely.
Examples:
Loop where the loop‐variable is unused:
static int count(Iterable<Order> orders) {
int total = 0;
for (Order _ : orders) // unnamed variable
total++;
return total;
}
Queue removal where third remove result is ignored:
Queue<Integer> q = ...;
while (q.size() >= 3) {
var x = q.remove();
var y = q.remove();
var _ = q.remove(); // unnamed variable for ignored removal
... new Point(x, y) ...
}
Catch clause where exception is unused:
try {
int i = Integer.parseInt(s);
… i …
} catch (NumberFormatException _) { // unnamed variable
System.out.println("Bad number: " + s);
}
Lambda parameter unused:
stream.collect(Collectors.toMap(
String::toUpperCase,
_ -> "NODATA" // underscore parameter because parameter not used
));
Try-with-resources when resource is acquired but not used:
try (var _ = ScopedContext.acquire()) { // unnamed variable
… no use of acquired context …
}
2. Unnamed Pattern Variables & Unnamed Pattern
In pattern matching (type patterns, instanceof, record patterns, switch patterns) you often declare a pattern variable that binds the matched value. If you intend not to use that bound variable, you can use _ instead of a named variable.
Usage:
- In a type pattern:
T _replacesT identifierwhen you don’t need the identifier. - In a record pattern: you may omit binding names for components you don’t use; you may use
_and also omit whole nested patterns if nothing inside needs to be bound. - The unnamed pattern (just
_) matches anything and binds nothing; it cannot be used as a top‐level pattern ininstanceofor acaselabel, but can be nested within record patterns as the ignoring placeholder.
Examples:
Given:
sealed abstract class Ball permits RedBall, BlueBall, GreenBall { }
final class RedBall extends Ball { }
final class BlueBall extends Ball { }
final class GreenBall extends Ball { }
Ball ball = ...;
switch (ball) {
case RedBall _ -> process(ball);
case BlueBall _ -> process(ball);
case GreenBall _ -> stopProcessing();
}
Here _ means “I don’t care about the binding variable.”
Nested example with Box:
record Box<T extends Ball>(T content) { }
Box<? extends Ball> box = ...;
switch (box) {
case Box(RedBall _) -> processBox(box);
case Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(var _) -> pickAnotherBox();
}
Here the last case uses var _, which is equivalent to an unnamed pattern variable; with JEP 456 you can use simply _.
Ommiting an entire nested pattern with _:
if (r instanceof ColoredPoint(Point(int x, int y), _)) {
… x … y …
}
Here the _ corresponds to the Color c part (unused).
And if you only care about a deeply nested part:
if (r instanceof ColoredPoint(Point(int x, _), _)) {
… x …
}
You extract only x, ignoring y and Color c.
3. Multiple Patterns in a Single case Label
Traditionally, a case label in a pattern‐switch could have exactly one pattern. With JEP 456 you may write:
case Pattern1, Pattern2 -> action;
provided none of those patterns declare pattern variables (i.e., they don’t bind named variables).
For example:
switch (box) {
case Box(RedBall _), Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
Here the first case lumps together two patterns leading to the same result. Because the patterns use _ (unnamed pattern variable) they don’t declare named variables—so the rule is satisfied.
Grammar change snippet (from JEP):
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern {, CasePattern} [Guard]
default
And the semantic rule: matching if the value matches any of the patterns in the label.
Important compile‐time restriction: If a case label has multiple patterns then it is a compile‐time error for any of the patterns to declare any pattern variables.
And regarding guards: the guard applies for the whole label, not individually to each pattern. For example:
case Box(RedBall _), Box(BlueBall _) when x == 42 -> processBox(b);
is valid. But you cannot write:
case Box(RedBall _) when x == 0, Box(BlueBall _) when x == 42 -> … // error
How to adopt JEP 456 in your code — with examples and best practices
Here are step-by-step instructions, common patterns, pitfalls and tips for using static analysis, style, and mixing with existing code.
Step 1: Update to Java 22
Ensure your project is using JDK 22 (or higher) and that the compiler is set to allow the feature (it is finalized, so no preview flag is needed). JEP 456 is marked as “Delivered” for release 22.
Step 2: Identify places where variables/patterns are unused
Look through your codebase for patterns such as:
- Loop variables that are declared but not used.
- Try-with-resources where the resource name is not used in the body.
- Catch clauses where the exception parameter is unused.
- Lambda parameters where the parameter name is not used inside the lambda.
- Pattern matching with nested record patterns where some components are unused.
Step 3: Replace named variables with _ where appropriate
Guideline: Use _ when you are sure the binding is not going to be used.
Example 1 – Enhanced for loop ignoring loop variable:
for (Order _ : orders) {
total++;
}
Example 2 – Basic for loop ignoring side‐effect variable:
for (int i = 0, _ = sideEffect(); i < 10; i++) {
… i …
}
Example 3 – Catch where exception parameter is unused:
try {
…
} catch (NumberFormatException _) {
System.out.println("Bad input");
}
Example 4 – Lambda ignoring parameter:
list.stream()
.map(_ -> _someConstant) // but careful: in this example the _ would shadow something?
…
Better:
list.forEach(_ -> System.out.println("hello"));
Here the parameter is logically unnamed.
Example 5 – Resource in try-with-resources:
try (var _ = context.acquire()) {
// resource closed automatically but not referenced
…
}
Step 4: Replace pattern bindings with _ or omit them
Case A – Simple type pattern binding unused:
if (obj instanceof String _) {
// ignore the string, but match that obj is a String
}
Case B – Nested patterns in record patterns:
Given:
record Point(int x, int y) {}
record ColoredPoint(Point p, Color c) {}
…
if (r instanceof ColoredPoint(Point(int x, int y), Color c)) {
… use x & y …
}
If you don’t use c, better:
if (r instanceof ColoredPoint(Point(int x, int y), _)) {
… x … y …
}
If you only care about x:
if (r instanceof ColoredPoint(Point(int x, _), _)) {
… x …
}
Case C – Switch with multiple patterns:
switch (box) {
case Box(RedBall _), Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
Here the first case uses two patterns, neither binds a named variable. Good.
Step 5: Style & readability considerations
- Using
_clearly signals “I do not need this binding” — this helps maintainers immediately see intent. - Don’t over-apply: If you might need the binding later or the code is still evolving, you may want to keep the named variable.
- When you switch to
_, ensure no accidental uses of that variable (there won’t be any, because_binds nothing, but you should adjust the body accordingly). - Be aware of static analysis tools: some older tools might not recognise
_as a special “unused” binding and may still warn. The JEP discusses this risk. - Maintain consistency in your codebase: perhaps adopt a style guideline for when to use
_. Use comments or code review to reinforce. - Note: Using
_as a variable name was disallowed (without error) in Java 9 and later (JEP 213) for identifiers “_”. Typical code should avoid naming a variable literally_as identifier; now it’s repurposed.
Step 6: Tools & backward‐compatibility
- Since the feature is finalized, no preview flags are needed.
- Code compiled in Java 22 with
_for unnamed variables will run on Java 22+. If you target earlier versions (Java 21 or lower), you can’t use this feature. - Review your build and CI to ensure the compiler version is correct.
- Check your IDE: ensure your IDE (e.g., IntelliJ, Eclipse, VSCode) supports the syntax so you don’t get erroneous errors.
- Static analysis / lint tools may need to be updated to recognise
_as a special ignore‐binding; otherwise you may see “unused variable” warnings even though you intended _ to represent “not used”.
Things to watch out / pitfalls
- Shadowing/Name collisions: Since
_does not introduce a name, you cannot refer to it later. That means you cannot accidentally reuse it. But sometimes you might think you bound something and then refer to it—this will not compile, so be aware. - Using
_in contexts where binding is used: If you use_but then try to refer to the binding, you’ll get an error. So ensure you only use_when you really don’t need the binding. - Multiple patterns requiring bindings: You cannot combine patterns that declare named variables in a multi‐pattern
caselabel. E.g., you cannot do:case PatternA(var x) , PatternB(var y) -> … // error: patterns declare variablesBecause rule is: if multiple patterns in one case, none may declare pattern variables. - Unnamed pattern vs top‐level pattern: The “unnamed pattern” (just
_) cannot be used as a top‐level pattern ininstanceofor acaselabel; it is only allowed nested inside record patterns. - Interoperability with older code: If you have frameworks or libraries doing reflection or expecting named parameters etc., you might want to ensure there’s no wacky behavior (though this feature is purely compile-time binding elimination, so at runtime there’s no variable name to care about).
- Style consistency: Using
_all over might reduce readability if overused. For example, in a loop where you could use the variable for debugging or future requirements, giving it a name might be better.
Summary checklist
- Ensure you are compiling with Java 22 or later.
- Identify variable declarations, loop headers, catch parameters, lambda parameters, resource variables, pattern variables that are never used.
- Replace unused names with
_. - In pattern matching (record patterns, type patterns), replace unused pattern variables with
_(unnamed pattern variables). - In a
switchwith patterns, if you have several patterns that lead to the same action, combine them usingcase Pattern1, Pattern2 -> …, but only if none of them bind named variables. - Update tooling and linters so they recognize
_as a special “unused binding” rather than erroneously flagging it. - Review style guidelines: decide when to keep a name vs. when to use
_. - Test and compile to make sure nothing breaks (especially if migrating code).
- Avoid naming fields or method parameters
_— this feature is only for local variables, resource variables, catch parameters, lambda parameters and pattern variables.
Example: Full small demo
Here’s a small illustrative demo combining multiple aspects of JEP 456.
record Point(int x, int y) { }
record ColoredPoint(Point p, Color c) { }
sealed abstract class Ball permits RedBall, BlueBall, GreenBall { }
final class RedBall extends Ball { }
final class BlueBall extends Ball { }
final class GreenBall extends Ball { }
public class JEP456Demo {
static int count(Iterable<Order> orders) {
int total = 0;
for (Order _ : orders) { // unnamed loop variable
total++;
}
return total;
}
void processQueue(Queue<Integer> q) {
while (q.size() >= 3) {
var x = q.remove();
var _ = q.remove(); // unnamed variable
var _2 = q.remove(); // you could use _ again, but maybe use different naming – though you *can* reuse `_`
// Actually you *can* use multiple unnamed: var _ = …
… new Point(x, 0) …
}
}
void catchDemo(String s) {
try {
int i = Integer.parseInt(s);
System.out.println("Parsed: " + i);
} catch (NumberFormatException _) { // unnamed exception parameter
System.out.println("Bad number: " + s);
}
}
void lambdaDemo(List<String> list) {
list.forEach(_ -> System.out.println("hello")); // unnamed parameter
}
void switchPatternDemo(Box<? extends Ball> box) {
switch (box) {
case Box(RedBall _) , Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
}
void instanceOfDemo(Object r) {
if (r instanceof ColoredPoint(Point(int x, _), _)) {
System.out.println("Only x = " + x);
}
}
// placeholder methods
void processBox(Box<? extends Ball> b) { /* … */ }
void stopProcessing() { /* … */ }
void pickAnotherBox() { /* … */ }
}
In this demo you can see:
- Unnamed variables (
_) in loop, variable decl, catch, lambda. - Unnamed pattern variables (
RedBall _,BlueBall _,Box(_),Point(int x, _), second_). - Multi‐pattern
caselabel combining two patterns into one case.
Final Thoughts
JEP 456 is a relatively small language extension in terms of size, but it addresses a real pain point in modern Java code — namely, the presence of required but never-used variables and pattern bindings, and the associated noise in code and tooling. By enabling _ as a first‐class “unused binding” placeholder, it improves readability, signals intent clearly, and helps reduce accidental misuse.
When adopting it in your codebase:
- Use it in places where the binding truly doesn’t matter.
- Keep your style consistent.
- Update tooling.
- Don’t force it where variable names would actually provide clarity or future utility.
