Skip to content

Unnamed Variables & Patterns in Java (JEP 456)

Seven dominoes in a row on apricot – the middle one is a blank tile without pips, glowing warmly

In this article, you will learn:

  • What is an unnamed variable, and what purpose does it serve?
  • What are unnamed patterns and unnamed pattern variables, and what purpose do they serve?
  • How can you write switch more concisely with unnamed pattern variables?
  • Where is the underscore allowed, and where not?

Unnamed variables and patterns were introduced as a preview feature in Java 21 as part of Project Amber. They were finalized in Java 22.

Unnamed Variables

It often happens that we have to define a variable that we don’t need later. Here are two examples that you probably know:

Example 1: Exceptions – here, we don’t use e:

try {
  int number = Integer.parseInt(string);
} catch (NumberFormatException e) {
  System.err.println("Not a number");
}

Example 2: Map.computeIfAbsent() – this time, we don’t use k:

map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

As of Java 22 (or as of Java 21 with preview features enabled), we no longer have to name such variables but may (as has long been common in other programming languages) use the underscore (_) instead.

Here is the exception example with an unnamed variable:

try {
  int number = Integer.parseInt(string);
} catch (NumberFormatException _) {
  System.err.println("Not a number");
}

And the computeIfAbsent() example:

map.computeIfAbsent(key, _ -> new ArrayList<>()).add(value);

With an exception, one may argue about the pros and cons of an unnamed variable. We are used to naming an exception e or letting our IDE do that automatically.

With computeIfAbsent(), on the other hand, I always wondered how to name the variable I didn’t need. Sometimes it became a k (for “key”), sometimes an ignored, and sometimes a __ (double underscore). This is where the unnamed variable is a big help.

The underscore helps not only in catch blocks and lambdas. Let’s look at the other use cases.

The Loop Variable in a for Loop

Sometimes, in a for loop, we only care about how often something happens – not with what. Here’s an example: we want to count how many elements an Iterable contains. Since Iterable has no size() method, we have to count the elements by iterating over them – and to do that, we have to name a loop variable we don’t actually need:

int count = 0;
for (Order order : orders) {
  count++;
}

We don’t need the order variable – so we replace it with the underscore:

int count = 0;
for (Order _ : orders) {
  count++;
}

Local Variables Whose Value We Don’t Need

We can also leave an ordinary local variable unnamed – for example, when we’re only interested in the side effect of a method call. Imagine we take three elements from a queue but only need the first two:

while (queue.size() >= 3) {
  var first  = queue.remove();
  var second = queue.remove();
  var _      = queue.remove(); // removes the third element, which we don't need
  process(first, second);
}

A plain queue.remove(); without an assignment would work too. With var _, however, the three lines keep the same shape, and a static analysis tool has no ignored return value to report.

Resources in try-with-resources

A try-with-resources block closes its resource automatically – even if we never access it directly within the block. A typical example is a custom lock class: its acquire() method locks the lock and returns an AutoCloseable whose close() releases the lock again – the JDK’s java.util.concurrent.locks.Lock itself is not AutoCloseable.

This is what the block looks like with an unnamed resource:

try (var _ = lock.acquire()) {
  // critical section – close() releases the lock on exit
}

With that, you make it clear: the resource matters because of its side effect (acquiring and releasing it), but we don’t access it within the block.

Multiple Underscores Side by Side

An underscore doesn’t introduce a name, so it doesn’t shadow any other variable either. You may use it as often as you like in the same block. If we need only the first of every three elements from the queue above, two unnamed variables stand one below the other:

while (queue.size() >= 3) {
  var first = queue.remove();
  var _     = queue.remove();
  var _     = queue.remove();
  process(first);
}

The same applies to the parameter list of a lambda. Map.replaceAll() passes key and value to the lambda; if we want to set all values to 0, we need neither of them:

map.replaceAll((_, _) -> 0);

Unnamed Patterns and Pattern Variables

The feature is not called “Unnamed Variables,” but “Unnamed Variables & Patterns” and thus has a lot more to offer – namely in combination with the features Record Patterns and Pattern Matching for Switch, which were finalized in Java 21.

The variable y is not needed in the “then block” of the following code example:

if (object instanceof Position(int x, int y)) {
  System.out.println("object is a position, x = " + x);
}

Again, we can replace y with an underscore:

if (object instanceof Position(int x, int _)) {
  System.out.println("object is a position, x = " + x);
}

This is called an “unnamed pattern variable.”

We can even go one step further and replace the entire partial pattern int y with an underscore:

if (object instanceof Position(int x, _)) {
  System.out.println("object is a position, x = " + x);
}

This is called an “unnamed pattern.”

In the previous example, this doesn’t have much effect; however, using nested patterns, it can save a lot of space. In the following example, we use only the variables x1 and y1, while x2 and y2 are unused:

if (object instanceof Path(Position(int x1, int y1), Position(int x2, int y2))) {
  System.out.printf("object is a path starting at x = %d, y = %d%n", x1, y1);
}

Here we can replace the complete second Position pattern with the underscore:

if (object instanceof Path(Position(int x1, int y1), _)) {
  System.out.printf("object is a path starting at x = %d, y = %d%n", x1, y1);
}

That, after all, represents a significant improvement.

Unnamed Pattern Variables and Pattern Matching for Switch

Here is an example with unused variables in Pattern Matching for Switch:

switch (obj) {
  case Byte    b -> System.out.println("Integer number");
  case Short   s -> System.out.println("Integer number");
  case Integer i -> System.out.println("Integer number");
  case Long    l -> System.out.println("Integer number");

  case Float  f -> System.out.println("Floating point number");
  case Double d -> System.out.println("Floating point number");

  default -> System.out.println("Not a number");
}

Again, we may replace all variable names with underscores:

switch (obj) {
  case Byte    _ -> System.out.println("Integer number");
  case Short   _ -> System.out.println("Integer number");
  case Integer _ -> System.out.println("Integer number");
  case Long    _ -> System.out.println("Integer number");

  case Float  _ -> System.out.println("Floating point number");
  case Double _ -> System.out.println("Floating point number");

  default -> System.out.println("Not a number");
}

We can even go one step further and combine all cases with the same actions:

switch (obj) {
  case Byte _, Short _, Integer _, Long _ -> System.out.println("Integer number");
  case Float _, Double _ -> System.out.println("Floating point number");

  default -> System.out.println("Not a number");
}

And this is – besides the more concise notation – the second big advantage of the unnamed pattern variable! With named variables, this would not have been possible. The following code is not valid:

switch (obj) {
  // Not allowed!
  case Byte b, Short s, Integer i, Long l -> System.out.println("Integer number");
  case Float f, Double d -> System.out.println("Floating point number");

  default -> System.out.println("Not a number");
}

This code results in the following compiler error:

error: illegal fall-through from a pattern
  case Byte b, Short s, Integer i, Long l -> System.out.println("Integer number");
               ^
  (the current case label is missing a break)

The compiler reports the same error for the Float/Double line. The javac compiler has added the hint about a missing break since Java 23; with the arrow notation, it is misleading, as no break is missing here.

The crucial difference is that named variables can be accessed from subsequent code, while unnamed variables may not be accessed. Since the compiler does not know which pattern will match at runtime, it also does not know which of the variables b, s, i, and l may be accessed. Therefore, in a case label with multiple patterns, it forbids named pattern variables altogether – even a single one, as in case Byte b, Short _. Unnamed pattern variables, on the other hand, may appear in it as often as you like.

By the way, multiple patterns in one case label have only been possible since this feature. Such a label may also have a guard (when …), which then applies to all patterns of the label together.

Where the Underscore Is Allowed and Where Not

The underscore exists only for variables that nobody sees outside their method. As an unnamed variable, you may declare it in six places:

  • as a local variable,
  • as a resource in try-with-resources,
  • in the header of a basic for loop, e.g., for (int i = 0, _ = sideEffect(); i < 10; i++),
  • as the variable of an enhanced for loop,
  • as the exception parameter of a catch block,
  • as a lambda parameter.

In addition, there are the unnamed pattern variable in a type pattern (Integer _) and the unnamed pattern _ in a record pattern.

The following table shows which attempts the compiler rejects and with which message (tested with Java 27):

PlaceExampleError message
Fieldprivate int _ = 1;underscore not allowed here
Method parametervoid log(String _)underscore not allowed here
Local variable without initializerint _;= expected
Read accessSystem.out.println(_);underscore not allowed here
Unnamed pattern at the top level (instead of in a record pattern)o instanceof _, case _ ->underscore not allowed here

JEP 456 deliberately excludes fields and method parameters:

  • A field stores a value that other methods of the object read later – no method could read a field without a name.
  • For an unnamed method parameter, it would be unclear what applies when overriding and what its Javadoc looks like. According to the JEP, unnamed method parameters may become the subject of a future JEP.

At the top level, instead of _, you write a type pattern with an unnamed pattern variable, e.g., o instanceof String _ or case String _ ->.

Conclusion

Unnamed variables and patterns solve a small but everyday annoyance: time and again, we have to declare variables that we don’t use at all – the exception in the catch block, the key in computeIfAbsent(), the loop variable when we’re just counting.

With the underscore, you explicitly show that a variable is intentionally left unused. Your code becomes more concise and, at the same time, more expressive.

On top of that, you can combine multiple patterns with the same code in one case label – which works only with unnamed pattern variables, not with named ones.

Fields and method parameters, on the other hand, remain named: the underscore is limited to variables that nobody sees outside their method.

Unnamed Variables & Patterns are defined in JDK Enhancement Proposal 456.

Did this article save you time? Then I’d be happy if you invested a minute of it in a review on my ProvenExpert profile. Your feedback shows me that the work on these articles pays off.

👉 Leave a review

This subject in your own code?

You have read the article – in the training your team works with it. Over 2 days we go through the subjects on your own projects instead of constructed examples.

Hands-on, easy to understand, and directly applicable to your day-to-day project work. Instead of theory, I teach principles that help you write code that is better, more maintainable, and more performant in the long run.

Java 25 TrainingSee all trainings

Become a Better Java Developer

My free newsletter keeps you ahead. Modern Java: new versions & features, performance, and JVM insights – once a month.

Search