Handling Null in Switch

Naveen AnamaneniUpdated · 6 min read

For most of Java's history, passing null to a switch always threw a NullPointerException, so code needed an if (x == null) check first. Since Java 21, a switch can handle null directly with a case null label. In this lesson, you will learn why switch rejects null by default, how to use case null and case null, default, and how null behaves in instanceof and in record patterns, where the rules are less obvious.

Why Switch Throws on Null

Let's see an example of a classic String switch receiving null:

OldSwitch.java▶ Try it
public class OldSwitch {
    static String day(String code) {
        switch (code) {
            case "MON": return "Monday";
            default: return "Other";
        }
    }
 
    public static void main(String[] args) {
        try {
            day(null);
        } catch (NullPointerException e) {
            System.out.println("NPE: " + e.getMessage());
        }
    }
}
Output
NPE: Cannot invoke "String.hashCode()" because "<local1>" is null

Explanation: A String switch is compiled to a switch on code.hashCode(), so null fails there. The message even shows that hidden call; <local1> is a temporary variable the compiler created. Even default doesn't catch null: default never matches null. Enum switches fail the same way. A pattern switch without case null also throws, through the Objects.requireNonNull check it starts with.

Using case null

NullCases.java▶ Try it
public class NullCases {
    enum Size { SMALL, LARGE }
 
    static String describe(Object o) {
        return switch (o) {
            case null -> "nothing there";
            case String s -> "String " + s;
            default -> "other";
        };
    }
 
    static String either(Object o) {
        return switch (o) {
            case String s -> "String " + s;
            case null, default -> "null or other: " + o;
        };
    }
 
    static String size(Size s) {
        return switch (s) {
            case SMALL -> "S";
            case LARGE -> "L";
            case null -> "no size chosen";
        };
    }
 
    public static void main(String[] args) {
        System.out.println(describe(null));
        System.out.println(either(null));
        System.out.println(either(42));
        System.out.println(size(null));
        try {
            Object x = null;
            String r = switch (x) {
                case String s -> "s";
                default -> "d";
            };
        } catch (NullPointerException e) {
            System.out.println("no case null: NullPointerException");
        }
    }
}
Output
nothing there
null or other: null
null or other: 42
no size chosen
no case null: NullPointerException

Explanation:

  • case null works like any other case and can be placed anywhere, though putting it first is clearest.
  • case null, default sends null and every unmatched value to the same body. It must be the last case.
  • Enums and strings accept case null too, so the old if (s == null) check before the switch is no longer needed.
  • Without case null, the switch still throws NullPointerException, even when there is a default. This keeps old code behaving as before.

What You Can't Combine

null can be combined with default, but not with a pattern:

Compiler output
N2.java:4: error: invalid case label combination
            case null, String s -> "x";
                       ^
1 error

If null matched String s, then s would be null inside a case that promises a String, which defeats the purpose of the pattern.

Null in Instanceof

instanceof never throws on null. It simply returns false, so a pattern never binds a variable to null:

Snippet.java
System.out.println(null instanceof String);   // false

Null Inside Record Patterns

Record patterns (see "Record Patterns in Java") look inside a record's components, and a component can be null. The rule: a nested type pattern matches null only if it is total, that is, if its type covers the component's declared type completely.

NestedNull.java▶ Try it
public class NestedNull {
    record Box(Object content) { }
    record Label(String text) { }
 
    public static void main(String[] args) {
        Object b = new Box(null);
        if (b instanceof Box(Object c)) {
            System.out.println("Box(Object c) matched, c = " + c);
        }
        if (b instanceof Box(String c)) {
            System.out.println("Box(String c) matched");
        } else {
            System.out.println("Box(String c) did not match");
        }
        Object l = new Label(null);
        if (l instanceof Label(String t)) {
            System.out.println("Label(String t) matched, t = " + t);
        }
    }
}
Output
Box(Object c) matched, c = null
Box(String c) did not match
Label(String t) matched, t = null

Explanation: Box's component is declared as Object. Box(Object c) covers every possible value, including null, so it matches and c is null. Box(String c) is a real type test on the component, and null isn't a String, so it doesn't match. Label's component is declared as String, so Label(String t) is total and matches null too. The outer record must still be non-null; only the components can be.

Common Mistakes

Assuming default handles null. It doesn't; add case null.

Writing case null, String s. Only case null, default is allowed.

Assuming nested pattern variables are never null. Total nested patterns do match null components.

Interview Corner

How does switch handle null in modern Java?

By default, a switch throws NullPointerException when the selector is null, even if it has a default label, because default never matches null. Since Java 21 you can add case null to handle it, or case null, default to treat it like any unmatched value. case null works for pattern switches, strings and enums. It can't be combined with a type pattern. Separately, instanceof returns false for null, and nested total patterns inside record patterns can match null components.

Practice

  1. Easy: Rewrite a method that starts with if (status == null) return "unknown"; followed by an enum switch, using case null instead.
  2. Harder: Create record Pair(Object left, Object right) and test which of Pair(Object a, Object b), Pair(String a, var b) and Pair(var a, var b) match new Pair(null, null). Explain each result.

Quick check: Null in switch

4 questions
1A switch has a default case and receives null. What happens?
2Which label combination is allowed?
3What does null instanceof String return?
4Does Label(String t) match new Label(null) when text is declared as String?

Key Takeaways

Was this tutorial helpful?

Lesson 458 of 482