JDK-8351870 : Casting null to Long when using a ternary conditional operator
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 21
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: generic
  • CPU: generic
  • Submitted: 2025-03-11
  • Updated: 2025-06-20
  • Resolved: 2025-03-13
Related Reports
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
JDK: Eclipse Adoptium JDK-21.0.3.9-Hotspot

A DESCRIPTION OF THE PROBLEM :
In a ternary conditional construction, if the return value might either be a primitive long or null, the compiler does not throw an error.

A primitive long and null are incompatible. Hence, it is expected that the behavior of the compiler is similar to how it would behave if the return value might have been "a primitive long or a string". In case of "a primitive long or a string", the compiler throws an error like:
>>
java: incompatible types: bad type in conditional expression
    bad type in conditional expression
      java.lang.String cannot be converted to java.lang.Long
<<

Example code to reproduce the issue:
>>
Object a = null;
final Long b = a instanceof Long ? Long.class.cast(a).longValue() : a instanceof Integer ? Integer.class.cast(a).longValue() : null;
<<

The example throws a
>>
java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()"
<<
on runtime. 




Comments
[~jhendrikx] I believe this is just autounboxing for an Integer value. If you change int x into var x, you should find x's type to be java.lang.Integer.
02-05-2025

Just noticed that this obviously wrong code will also compile: int x = 2 > 3 ? 1 : null It seems to be a different case then presented in this ticket (where a boxed type is used as the target). The above produces an NPE at runtime if it goes into the null ternary branch. I wrote something similar during a refactor, and missed the `null`. There are no boxed primitives involved directly, but apparently due to use of `null` there is boxing happening. ECJ also allows it.
02-05-2025

I think the forward references to h are OK because they are unreachable, so DA/DU analysis doesn't "go there": According to §16: • Every local variable ... must have a definitely assigned value when any access of its value occurs. • An access to its value consists of the simple name of the variable ... occurring anywhere in an expression except as the left-hand operand of the simple assignment operator. • For every access of a local variable declared by a statement x, ... x must be definitely assigned before the access. Consider this simpler example: Integer h = false ? h.hashCode() : null; The question is: Is h DA at the point of the access to h in "h.hashCode()"? - Initially h is unassigned. - By §16.1.5 (9th bullet point): h is assigned before "h.hashCode()" iff h is assigned after "false" when true - By §16.1.1 (2nd bullet point): h is assigned after any constant expression whose value is false when true Therefore h is DA before the access to h in "h.hashCode()". The same thing happens with this example, which also compiles: Integer h; if (false) h = h.hashCode(); else h = null; So in summary there don't seem to be any bugs here.
14-03-2025

I just noted that [~acobbs] discovered another forward reference issue in the use of conditional operators. That seems to be a legitimate issue, but it should belong to its own issue instead of this one.
13-03-2025

Ah OK I see. It's forced to a primitive "int". Thanks.
13-03-2025

[~liach], Doesn't my example contain an illegal forward reference to "h"? Or maybe it doesn't because "false" is a constant value... But if that's true, then there should be no NPE at runtime - because "h.hashCode()" should never be executed... right?
13-03-2025

I think is is working as intended. Let's analyze "a instanceof Long ? Long.class.cast(a).longValue() : a instanceof Integer ? Integer.class.cast(a).longValue() : null": it's obvious that the last conditional op is (long, null), which produces java.lang.Long. Then the first conditional op is (long, Long), which produces long, and there is a `longValue()` call on the `java.lang.Long. And that's where the NPE happened. The type flow is cond(long, cond(long, null)) = cond(long, Long) = long. Same for [~acobbs]'s example: it's cond(int, cond(int, null)) -> cond(int, Integer) -> int. See JLS 15.25.
13-03-2025

Even weirder... this program shouldn't even compile, must less run: $ cat Test.java public class Test { public static void main(String[] args) { Integer h = false ? h.hashCode() : false ? h.hashCode() : null; } } $ javac Test.java $ java Test Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "null" is null at Test.main(Test.java:3) $ java -version openjdk version "24" 2025-03-18 OpenJDK Runtime Environment (build 24+36-3646) OpenJDK 64-Bit Server VM (build 24+36-3646, mixed mode, sharing) Same thing happens all the way back to JDK 8.
13-03-2025

This looks like a bug to me, so I've changed it from Enhancement to Bug and moved it to tools/javac for the compiler team's input. This reproducer test program: public class Test { public static void main(final String[] args) throws Exception { Object a = null; final Long b = a instanceof Long ? Long.class.cast(a).longValue() : a instanceof Integer ? Integer.class.cast(a).longValue() : null; System.out.println(b); } } results in a: Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" at Test.main(Test.java:4) I wouldn't have expected it to call longValue() on the null instance. In fact, even the following variation of the program throws a NullPointerException: public class Test { public static void main(final String[] args) throws Exception { Object a = null; final Long b = a instanceof Long ? 42L : (a instanceof Integer ? 23L : null); System.out.println(b); } } results in: Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" at Test.main(Test.java:4) Trying to do a similar thing without the ternary operator works fine without any exceptions: public class Test { public static void main(final String[] args) throws Exception { Object a = null; final Long b; if (a instanceof Long) { b = 42L; } else if (a instanceof Integer) { b = 23L; } else { b = null; } System.out.println(b); } } returns: null
13-03-2025

Moving to JDK for further evaluation.
12-03-2025