Skip to content

IfElseIfConstructToSwitch does not handle null safety when converting instanceof chains #1013

@sergeykad

Description

@sergeykad

What happened

When IfElseIfConstructToSwitch (used by org.openrewrite.java.migrate.UpgradeToJava21) converts if-else instanceof chains to switch pattern matching, it does not account for null safety differences between the two constructs.

if (obj instanceof X) safely returns false when obj is null, but switch (obj) throws a NullPointerException unless a case null is explicitly present. This introduces a runtime regression when the switched variable can be null.

Example

Original code (null-safe):

Object existingValue = wrapper.getPropertyValue(key); // can return null

if (existingValue instanceof Map map) {
    map.putAll((Map) v);
} else if (existingValue instanceof Collection collection) {
    // handle collection
} else {
    // handles null safely — instanceof returns false, falls here
    patchedValue = v;
}

After conversion (throws NPE when existingValue is null):

switch (existingValue) {
    case Map map -> map.putAll((Map) v);
    case Collection collection -> { /* handle collection */ }
    default -> patchedValue = v; // never reached if null — NPE thrown before
}

Expected conversion:

switch (existingValue) {
    case Map map -> map.putAll((Map) v);
    case Collection collection -> { /* handle collection */ }
    case null, default -> patchedValue = v;
}

Impact

In our codebase, running UpgradeToJava21 introduced this issue in several converted switch statements where the switched variable could be null at runtime. One was caught immediately by a failing test; the others were found by manual review.

The recipe should either:

  1. Emit case null, default instead of default when converting instanceof chains (since instanceof is inherently null-safe), or
  2. At minimum, emit case null, default when the switched variable is not a method parameter annotated @NonNull or otherwise guaranteed non-null

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions