Learn Java

Switch Pattern Matching in Java

The release of Java 17 has brought us new features expanding switch functionality. Since this version, we have pattern matching as a preview feature. This update will expand our capabilities when working with switch. In this topic, we will explore these enhancements to help you master them.

Pattern matching semantics

The pattern matching implementation for switch is based on the instanceof operator enhancements. It allows us to perform runtime type checking and other required operations, for example, printing a message. This feature can improve code readability and achieve the same functionality with fewer lines of code.

Let's look at the code snippet below with if blocks. Later we will write the same code using the new switch feature.

public class SwitchPatternMatchingDemo {
    public static void main(String[] args) {
        Object obj = "Java";
        ifTypeCheckingDemo(obj); // String: Java
    }

    static void ifTypeCheckingDemo(Object o) {
        if (o instanceof Integer i) {
            System.out.printf("int: %d", i);
        } else if (o instanceof Double d) {
            System.out.printf("double: %f", d);
        } else if (o instanceof String s) {
            System.out.printf("String: %s", s);
        } else {
            System.out.printf("No Match!");
        }
    }
}

In this example, we pass a variable to the ifTypeCheckingDemo(Object o) method, where it is checked against three specific data types and the method prints a corresponding message. In our case, that message is String: Java. Now, take a look at the same piece of code written using switch pattern matching:

public class SwitchPatternMatchingDemo {
    public static void main(String[] args) {
        Object obj = "Java";
        switchTypeCheckingDemo(obj); // String: Java
    }

    static void switchTypeCheckingDemo(Object o) {
        switch (o) {
            case Integer i -> System.out.printf("int: %d", i);
            case Double d  -> System.out.printf("double: %f", d);
            case String s  -> System.out.printf("String: %s", s);
            default        -> System.out.println("No Match!");
        }
    }
}

This code is much more concise and readable, which is a good reason to pay attention to this feature.

Selector expression type checking enhancement

The example in the previous section is written using basic Java types. But what if we need to perform type checking with another non-basic type? Before this update, switch supported only primitive types and their wrapper classes, as well as String and Enum type. This enhancement allows us to use any type in the scope of pattern matching.

public class SwitchPatternMatchingDemo {
    public static void main(String[] args) {
        Object obj = new Person("James Gosling");
        switchTypeCheckingDemo(obj); // Person: Person{name=James Gosling}
    }

    static void switchTypeCheckingDemo(Object o) {
        switch (o) {
            case Integer i -> System.out.printf("int: %d", i);
            case Person p  -> System.out.printf("Person: %s", p.toString());
            case String s  -> System.out.printf("String: %s", s);
            default        -> System.out.println("No Match!");
        }
    }
}

class Person {
    private String name;

    // getter and setter

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name=" + name +
                '}';
    }
}

This application will compile and print Person: Person{name=James Gosling}, which was impossible before the update.

Pattern matching via logical operators

Pattern matching for switch, like in the case of the instanceof operator, isn't limited to single type checking. We can also use the && logical operator and add an extra statement level:

public class SwitchPatternMatchingDemo {
    public static void main(String[] args) {
        Object obj = 100.0;
        switchTypeCheckingDemo(obj); // double: 100,000000, the number is positive
    }

    static void switchTypeCheckingDemo(Object o) {
        switch (o) {
            case Integer i -> {
                if (i > 0) {
                    System.out.printf("int: %d, the number is positive", i);
                }
            }
            case Double d && d > 0 -> System.out.printf("double: %f, the number is positive", d);
            default                -> System.out.println("No Match!");
        }
    }
}

Inside the switchTypeCheckingDemo(Object o) method there are two case labels. The first one uses a pattern to check the type, but the print statement is executed in the if block. The second case does the same thing but uses a pattern for the second statement to check whether the variable value is a positive number.

By the way, switch pattern matching, just like the instanceof operator, doesn't support the || operator due to its semantics.

Null operations

In older Java versions, switch statements couldn't handle null values. The following code block will run but throw a NullPointerException as soon as the application executes the first line of the nullDemo(String s) method.

public class SwitchPatternMatchingDemo {
    public static void main(String[] args) {
        String str = null;
        nullDemo(str);
    }

    static void nullDemo(String s) {
        switch (s) {
            case "Hello" -> System.out.println("null");
            case "Hi"    -> System.out.println("String");
            default      -> System.out.println("No Match!");
        }
    }
}

On the other hand, if str is not null but we add a null case, the code will not even run. We will get a compilation error:

public class SwitchPatternMatchingDemo {
    public static void main(String[] args) {
        String str = "Hello";
        nullDemo(str);
    }

    static void nullDemo(String s) {
        switch (s) {
            case null    -> System.out.println("null"); // Compilation error
            case "Hello" -> System.out.println("Hello");
            case "Hi"    -> System.out.println("Hi");
            default      -> System.out.println("No Match!");
        }
    }
}

The new features enable us to avoid these restrictions. We can have a null case label: the example below will compile successfully and we will face a NullPointerException only in one condition - if the selector expression is null and we don't have a null case.

publiec class SwitchPatternMathchingDemo {
    public static void main(String[] args) {
        String str = null;
        nullkDemo(str); // null case
    }

    static void nullDemo(String s) {
        switch (s) {
            case null    -> System.out.println("null case");
            case "Hello" -> System.out.println("Hello");
            case "Hi"    -> System.out.println("Hi");
            default      -> System.out.println("No Match!");
        }
    }
}

What do you think will be the result of running this application? Right: the code above will compile and print the null case message!

Conclusion

The enhancements introduced in Java 17 pattern matching for switch offer developers significant improvements in code clarity, flexibility, and maintainability. By allowing more concise syntax, support for complex data types, and even the ability to handle null cases safely, these updates enable more readable code, that improves overall code quality

Pattern matching also extends the power of switch beyond simple type checking, allowing logical operators like && to be incorporated and offering deeper flexibility in conditionals. Whether you're working with basic types, custom objects, or even null values, these new capabilities simplify many common programming tasks and eliminate potential errors like NullPointerExceptions.

As you continue to explore and utilize these features, you'll find that they not only reduce boilerplate code but also help improve the efficiency of your Java programs. Pattern matching for switch is a powerful tool that encourages more expressive and robust solutions for real-world software development challenges. Make sure to integrate these features into your workflow, and enjoy the enhanced programming experience they provide!

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate