1. Overview

In this lesson, we’ll explore Unnamed Variables and Patterns, a feature standardized in Java 22. This feature lets us mark variables or pattern components that are required syntactically but not needed in our logic — in the specific constructs that support unnamed variables.

The relevant module we need to import when starting this lesson is: unnamed-variables-and-patterns-start.

If we want to reference the fully implemented lesson, we can import: unnamed-variables-and-patterns-end.

2. The Problem: The “Ignored” Variable

It’s very common to write code where we’re forced to declare a variable that we never actually use. This leads to noisy code where we invent names like ignored, unused, or just e.

For instance, in a catch block, we might only care that an exception was thrown, not what it was:

try { 
    // ...
    int result = Integer.parseInt("not-a-number");
    // ... 
} 
catch (Exception ignored) { 
    System.out.println("Exception occurred in process!"); 
} 

In a lambda expression where a functional interface method forces extra parameters that we don’t need:

Map<String, Task> taskMap = new HashMap<>(Map.of("t1", new Task("t1", "My Task", "Desc", null)));
taskMap.replaceAll((key, task) ->
  new Task(task.getCode(), task.getName().toUpperCase(), task.getDescription(), task.getDueDate()));

Here, we updated the values of a map using the replaceAll() method. The method receives both the key and value, even though only the value is relevant. While harmless, these unused variables add visual clutter to the code. They can also trigger warnings in IDEs or static-analysis tools, which flag unused parameters as potential issues.

3. The Unnamed Variable: _

As a solution to this, Java introduced the underscore  “_” as the unnamed variable (finalized and generally available in Java 22). It’s a formal way to declare a variable that we intend to ignore.

By using _, we signal to the compiler and to other developers that the variable is intentionally unused and has no name.

It’s important to note the history of the underscore. In initial Java versions, _ was a valid variable name (identifier). In Java 8, using an underscore “_” as a variable name was deprecated and would produce a compiler warning, and Java 9 formally removed the use of _ as a variable name, throwing an actual compilation error if present.

This was done to “reserve” the underscore for its future use. As of Java 21, _ is a keyword reserved for unnamed variables and patterns.

4. Use Cases for Unnamed Variables

Since unnamed variables apply only in certain constructs — and not, for example, in regular method parameters — let’s look at the scenarios where they are valid.

4.1. In catch Blocks

First, we can use it within a catch block. We can replace our previous example catch block with _.

To demonstrate this, let’s add a test to NewJavaFeaturesUnitTest in the start module:

@Test 
void whenParsingInvalidNumber_thenExceptionIsHandledSilently() { 
    try { 
        int result = Integer.parseInt("not-a-number"); 
    } 
    catch (NumberFormatException _) { 
        System.out.println("Failed to parse number"); 
    }
} 

In the code above, we intentionally parse an invalid string. Then, in the catch block, we use _ to signal that we’re ignoring the Exception object, as we only care that the operation failed.

4.2. In Lambda Expressions

Furthermore, we can use _ to replace one or more unused lambda parameters.

Let’s add another test to NewJavaFeaturesUnitTest to demonstrate this scenario:

@Test
void whenReplacingValues_thenKeyIsIgnored() {
    Task task = new Task("t1", "My Task", "Desc", null);
    Map<String, Task> taskMap = new HashMap<>(Map.of("t1", task));

    taskMap.replaceAll((_, t) -> new Task(t.getCode(), t.getName()
      .toUpperCase(), t.getDescription(), t.getDueDate()));

    assertEquals("MY TASK", taskMap.get("t1").getName());
}

In the code above, Map.replaceAll() supplies both a key and a value. Since we only care about the value, we use _ to ignore the key.

4.3. In try-with-resources

Moreover, if we intend to open a resource for its side-effects (like initializing a database connection) but never reference the resource variable itself, we can use _.

Let’s demonstrate this by creating a helper class in the domain package:

class MyResource implements AutoCloseable {
    public MyResource() {
        System.out.println("Resource opened");
    }

    public void close() {
        System.out.println("Resource closed");
    }
}

Next, let’s write a unit test that uses the helper class for side effects:

@Test 
void whenUsingTryWithResources_thenResourceIsNotReferenced() { 
    try (var _ = new MyResource()) { 
        System.out.println("Inside try block"); 
    } 
} 

Here, the try-with-resources block creates a MyResource instance and ensures it’s automatically closed. By declaring the resource variable as _, we make it explicit that the variable is intentionally unused inside the block – we only care about the resource’s side effects.

4.4. In Local Variable Assignments

Sometimes we must call a method that returns a value, but we only care about its side effect (like queue.poll()). In many cases, we can simply call the method without assigning its return value at all.

However, some static analysis tools might flag this as a “return value ignored” warning. In these cases, we can explicitly assign the result to _ to signal that the return value is intentionally ignored.

Let’s add a test to NewJavaFeaturesUnitTest to see this:

@Test 
void whenRemovingFirstItem_thenRemovedValueIsIgnored() { 
    Queue<String> queue = new LinkedList<>(); 
    queue.add("first"); 
    queue.add("second");

    var _ = queue.poll();

    assertEquals(1, queue.size());
} 

Here, calling queue.poll() removes the head of the queue and returns it. Since we only care about the removal, we assign the return value to _.

5. Unnamed Patterns

We can also use the underscore _ as an unnamed pattern in instanceof checks and switch expressions. This is used to ignore a component of a pattern.

5.1. In switch Expressions

We can use the unnamed pattern in a switch when we need to match a type but don’t care about the variable itself. Let’s add a test to NewJavaFeaturesUnitTest that checks the type of an object:

@Test 
void whenSwitchingOnType_thenCorrectTypeIsMatched() { 
    Object obj = "Hello Baeldung"; 
    String result = "";

    switch (obj) {
        case Task _ -> result = "It's a Task";
        case String _ -> result = "It's a String";
        default -> result = "Other";
    }

    assertEquals("It's a String", result);
} 

In the code above, each case uses a type pattern – Task _ and String _. The pattern checks whether obj is an instance of the given type, but the _ indicates that we don’t need the bound variable in the case body. We’re only interested in the type match, not the value itself.

5.2. In Record Patterns (instanceof or switch)

If we only care about one of the components in a record, we can use _ to ignore the others.

To demonstrate this scenario, let’s create a record name TaskRecord in the domain package:

public record TaskRecord(String s, String myRecord) {
}

For instance, if we only want to validate that an object is a TaskRecord and get its name, we can ignore the code.

Let’s add a final test to NewJavaFeaturesUnitTest:

@Test 
void whenCheckingInstanceWithPattern_thenSpecificFieldIsExtracted() { 
    Object taskRec = new TaskRecord("task-1", "My Record");

    if (taskRec instanceof TaskRecord(var _, var name)) {
        assertEquals("My Record", name);
    } else {
        Assertions.fail("Pattern did not match");
    }
} 

In the code above, the if statement checks if the object is a TaskRecord and, if so, it uses the _ to ignore the first component (the code) while binding the name component to a new variable we can use in the if block.

We can use the same syntax within a switch block.

6. Conclusion

In this lesson, we explored Unnamed Variables and Patterns.

We learned how to use the underscore _ to replace variables that we are required to declare but don’t intend to use. We saw how this cleans up our code in unnamed variables (like catch blocks, lambdas, and loops) and in unnamed patterns (like switch and instanceof expressions).

For more details about these features, we can consult the JEP 456.