Command Palette

Search for a command to run...

PHASE 11Intermediate Java 22+ ~34 min· topic 4 of 6

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 _, like Circle _. It checks the type but keeps no variable.
Unnamed pattern
A bare _ inside a record pattern, like Point(var x, _). It matches any value in that position, even null, and binds nothing.
Keyword
A word the Java language reserves for itself, like class or if. 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.

terminal
$ javac -Xlint:all Main.java
── expected output ──
Main.java:35: warning: [try] auto-closeable resource unused is never referenced in body of corresponding try statement
try (Timer unused = new Timer()) {
^
1 warning

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.

Main.javawhole filejava
// 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 value

03Unnamed 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.

Main.javawhole filejava
// 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.

Main.javawhole filejava
// Java 22+
static String category(Event e) {
    return switch (e) {
        case Click _, Key _     -> "input";
        case Scroll _, Resize _ -> "layout";
    };
}
Unnamed pattern variables and several patterns per casediagram
Rendering diagram…

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:

terminal
$ javac --release 21 --enable-preview Main.java
── expected output ──
Main.java:4: error: as of release 21, the underscore keyword '_' is only allowed to declare
System.out.println(_);
^
unnamed patterns, local variables, exception parameters or lambda parameters
Note: Main.java uses preview features of Java SE 21.
Note: Recompile with -Xlint:preview for details.
1 error

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.

terminal
$ java Main.java # JDK 22 or newer
── expected output ──
click at 10,20
right click at 5,5
key A
key b
scroll up
window changed

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.

terminal
$ javac Main.java # JDK 8
── expected output ──
Main.java:3: warning: '_' used as an identifier
int _ = 5;
^
(use of '_' as an identifier might not be supported in releases after Java SE 8)
1 warning

Try it yourself

  1. 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.java and see which one the compiler warns about.

  2. 2

    Run the Java 22 version locally

    If you have JDK 22 or newer, save the unnamed-patterns example as Main.java and run java Main.java. Then add System.out.println(category(e)); inside the loop and predict the six new lines first.

  3. 3

    Try a binding in a multi-pattern case

    In the Java 22 example, change case Click _, Key _ to case Click c, Key _. Predict whether it compiles, then check the error.

Code & diagrams

Before Java 22: names for things you ignore New tab
Sign in to run this example in your browser.

Expected output

items: 3
parsed 42
'4x2' is not a number
qty 10
qty 4
timer started
doing work
timer stopped
The same program with unnamed variables Java 22+java

Run 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");
        }
    }
}
Java 21: unused components still need names Java 21+ New tab
This example needs Java 21+. The in-browser compiler is Java 17: install JDK 21 or newer and run it with "java Main.java".

Expected output

click at 10,20
right click at 5,5
key A
key b
scroll up
window changed
Java 22: unnamed patterns Java 22+java

Run 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).

terminal
$ javac Main.java # JDK 21
── what you'll see ──
Main.java:5: error: unnamed variables are a preview feature and are disabled by default.
} catch (NumberFormatException _) {
^
(use --enable-preview to enable unnamed variables)
1 error

Break #2

Read an unnamed variable

Declare int _ = 5; and then write System.out.println(_);.

terminal
$ javac --release 21 --enable-preview Main.java
── what you'll see ──
Main.java:4: error: as of release 21, the underscore keyword '_' is only allowed to declare
System.out.println(_);
^
unnamed patterns, local variables, exception parameters or lambda parameters
Note: Main.java uses preview features of Java SE 21.
Note: Recompile with -Xlint:preview for details.
1 error

Break #3

Bind a variable in a multi-pattern case

Write case Circle c, Square q -> "x"; in a switch over a sealed Shape.

terminal
$ javac --release 21 --enable-preview Main.java
── what you'll see ──
Main.java:7: error: illegal fall-through from a pattern
case Circle c, Square q -> "x";
^
Note: Main.java uses preview features of Java SE 21.
Note: Recompile with -Xlint:preview for details.
1 error

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 the LocalVariableTable debug info, so debuggers don't show it.

  • ▸

    The unnamed pattern _ is unconditional for its component type, so it matches null components, exactly like var x. That differs from a nested type pattern such as String _ on an Object component, 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 to default, 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. 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 enhanced for loop, an extra variable in a basic for header, a catch parameter, a try-with-resources resource, and a lambda parameter. Several _ can be declared in the same scope, because they never clash: (_, _) -> 0 is fine.

  2. 2

    An unnamed pattern variable is a type pattern with _ as its name: case Circle _ -> matches any Circle without binding it. The unnamed pattern is a bare _ inside a record pattern, which matches any component value, including null, without binding it: case Point(var x, _). It works like var unused but says what you mean, and it is only allowed nested inside a record pattern, never as a whole case label on its own.

  3. 3

    With unnamed pattern variables, a case may 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 reports illegal fall-through from a pattern.

  4. 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. 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

01

Where can _ be used as an unnamed variable, and where can't it?

02

What is the difference between an unnamed pattern variable (Circle _) and the unnamed pattern (_)?

03

Why are multiple patterns in one case allowed only when none binds a variable?

04

Why did Java make _ a keyword in Java 9, years before this feature?

Practice

01

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.

02

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.

03

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, not Exception) 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.