-
Notifications
You must be signed in to change notification settings - Fork 114
Description
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:
- Emit
case null, defaultinstead ofdefaultwhen convertinginstanceofchains (sinceinstanceofis inherently null-safe), or - At minimum, emit
case null, defaultwhen the switched variable is not a method parameter annotated@NonNullor otherwise guaranteed non-null
Metadata
Metadata
Assignees
Labels
Type
Projects
Status