Handling Null in Switch
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:
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());
}
}
}NPE: Cannot invoke "String.hashCode()" because "<local1>" is nullExplanation: 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
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");
}
}
}nothing there
null or other: null
null or other: 42
no size chosen
no case null: NullPointerExceptionExplanation:
case nullworks like any other case and can be placed anywhere, though putting it first is clearest.case null, defaultsendsnulland every unmatched value to the same body. It must be the last case.- Enums and strings accept
case nulltoo, so the oldif (s == null)check before the switch is no longer needed. - Without
case null, the switch still throwsNullPointerException, even when there is adefault. This keeps old code behaving as before.
What You Can't Combine
null can be combined with default, but not with a pattern:
N2.java:4: error: invalid case label combination
case null, String s -> "x";
^
1 errorIf 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:
System.out.println(null instanceof String); // falseNull 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.
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);
}
}
}Box(Object c) matched, c = null
Box(String c) did not match
Label(String t) matched, t = nullExplanation: 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
- Easy: Rewrite a method that starts with
if (status == null) return "unknown";followed by an enum switch, usingcase nullinstead. - Harder: Create
record Pair(Object left, Object right)and test which ofPair(Object a, Object b),Pair(String a, var b)andPair(var a, var b)matchnew Pair(null, null). Explain each result.
Key Takeaways
Was this tutorial helpful?