skip to content

How does a pattern switch handle a null selector, and what does `case null` (and `case null, default`) do?

level: middleimportance: should knowfreq 55%

answer

  1. No case null → null throws NPE (legacy behavior kept)
  2. case null matches ONLY null
  3. Type patterns never match null
  4. case null, default = only legal combination
  5. default alone does NOT catch null

basics

~20 s

By default a switch throws NullPointerException if the value is null. To handle null safely you add case null, which runs only when the value is null. You can also write case null, default to send null to the default branch.

solid answer

~50 s

Traditionally `switch(x)` throws a `NullPointerException` when `x` is null, before any case is examined. That behavior is preserved by default in pattern switches, so an unmatched null still throws. To handle null explicitly you write a dedicated `case null` label, which matches only the null value and lets you give it its own branch — removing the old need for a separate null check before the switch. You may combine it with default as `case null, default`, meaning "null and everything otherwise unmatched go here." `case null` is special: it is the only label that can match null, it may be combined only with `default` (not with other patterns), and putting it first is conventional and clear. The net effect is that null becomes a normal, expressible case in the switch rather than a trap that forces a pre-check, which is especially valuable when switching over an `Object` that may legitimately be null.

code

java · 10 lines
java
static String describe(Object o) {
    return switch (o) {
        case null      -> "null";          // only null matches here
        case Integer i -> "int " + i;
        case String s  -> "str " + s;
        default        -> "other";
    };
}
// Or fold null into default:
//   case null, default -> "null or anything else";

go deeper

for a junior

Knows the switch throws NPE on null unless you add case null.

for a middle

Uses case null and case null, default correctly and knows type patterns/default don't match null.

for a senior

Explains the backward-compatibility rationale, the legal label combinations, and how null fits exhaustiveness.

for a principal

Discusses modeling null as data in data-oriented APIs, when to forbid null at the boundary vs. handle it in the switch, and the trade-off of fail-fast vs. explicit handling.

## The historical null trap A `switch` evaluates its selector and then chooses a branch. For decades, if the selector was `null`, the switch threw a **`NullPointerException`** immediately — there was no way to write a `case` for null. So code had to guard *before* the switch: ```java if (s == null) { handleNull(); } else switch (s) { ... } ``` ## Default behavior is unchanged Pattern switches **keep** this default: if the selector is `null` and there is **no** `case null`, the switch still throws `NullPointerException`. This preserves backward compatibility — existing switches behave exactly as before. The NPE happens up front, regardless of which patterns are present. ## `case null` Java lets you add an explicit label `case null`, which **matches only the null value**: ```java static String describe(Object o) { return switch (o) { case null -> "it was null"; // only null lands here case Integer i -> "int " + i; case String s -> "str " + s; default -> "other"; }; } ``` Now null is just another branch; no NPE, no pre-check. Rules and conventions: - `case null` is the **only** label that can match `null`. A type pattern like `case String s` never matches null (mirroring how `instanceof` is false for null). - It is conventional (and clearest) to place `case null` **first**, though it is not strictly required to be first. - `case null` **cannot** be combined with arbitrary patterns. The **only** legal combination is with `default`: ```java case null, default -> "null or anything else unmatched"; ``` This single label means "if the value is null **or** nothing else matched, come here." It is the idiomatic way to make a switch null-tolerant while keeping one fallback. ## Why this design Making null an explicit, opt-in case (rather than silently swallowing it) keeps the old fail-fast behavior for code that didn't think about null, while giving new code a clean way to model null as data. This fits Java's data-oriented direction: a value of type `Object` legitimately includes `null`, so a switch over it should be able to name that possibility. ## Exhaustiveness and null For a pattern switch over a reference type, the compiler considers null only if you add `case null` (or `case null, default`); otherwise null is handled by the runtime NPE, not by exhaustiveness. A `default` alone does **not** catch null — null still throws unless you explicitly include `case null`. ## Deriving an answer Default: null → NPE (unchanged). Want to handle it? `case null` matches only null; combine with default as `case null, default`. Type patterns never match null, and a plain `default` does not catch null — only an explicit `case null` does.

  • Does adding a `default` branch protect a switch from a null selector?
    No. `default` does not match null; without an explicit `case null` the switch still throws NullPointerException for a null selector.
  • Can you write `case null, String s`?
    No. `case null` may only be combined with `default` (`case null, default`); it cannot be merged with type patterns. Use a separate `case null` label instead.

saying these in an interview costs you the question

  • Assuming `default` catches null (it does not — null still throws without case null)
  • Thinking a type pattern like `case String s` can match null
  • Believing pattern switches silently accept null now (they still NPE by default)
  • Trying to combine `case null` with a non-default pattern (only `case null, default` is allowed)

context