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, default instead of default when converting instanceof chains (since instanceof is inherently null-safe), or
- At minimum, emit
case null, default when the switched variable is not a method parameter annotated @NonNull or otherwise guaranteed non-null
What happened
When
IfElseIfConstructToSwitch(used byorg.openrewrite.java.migrate.UpgradeToJava21) convertsif-else instanceofchains toswitchpattern matching, it does not account for null safety differences between the two constructs.if (obj instanceof X)safely returnsfalsewhenobjisnull, butswitch (obj)throws aNullPointerExceptionunless acase nullis explicitly present. This introduces a runtime regression when the switched variable can benull.Example
Original code (null-safe):
After conversion (throws NPE when
existingValueis null):Expected conversion:
Impact
In our codebase, running
UpgradeToJava21introduced this issue in several converted switch statements where the switched variable could benullat runtime. One was caught immediately by a failing test; the others were found by manual review.The recipe should either:
case null, defaultinstead ofdefaultwhen convertinginstanceofchains (sinceinstanceofis inherently null-safe), orcase null, defaultwhen the switched variable is not a method parameter annotated@NonNullor otherwise guaranteed non-null