JDK-8150614 : conditional operators, null argument only for return purpose, and nullpointerexception
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 8u66,9
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: generic
  • CPU: generic
  • Submitted: 2016-02-23
  • Updated: 2016-05-26
  • Resolved: 2016-05-26
Description
FULL PRODUCT VERSION :
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b18)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b18, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.3.9600]

EXTRA RELEVANT SYSTEM CONFIGURATION :
I am using:
Eclipse Java EE IDE for Web Developers.

Version: Luna Service Release 2 (4.4.2)
Build id: 20150219-0600

A DESCRIPTION OF THE PROBLEM :
I have a method called toInteger to parse a string to a Integer. It receives two arguments, the string to parse and a replacement if the string cannot be parsed.
The arguments are:
    value : String, replace : Integer

the body method:
    return value == null ? replace : isInteger(value) ? Integer.parseInt(value) : replace;

*the method isInteger receives    string : String,    and returns boolean:
    return string != null && string.matches("-?\\d+");


I call toInteger passing a string and a null as a replacement.
In case that the string value is null, it has to return the null replacement, and in case the string value is not null nor a numeric string, it has to return the null replacement too. In any of that two cases it doesn't performs the Integer.parseInt method. But the call to toInteger with a null replacement throws a NullPointerException (not a NumberFormatException).

That is curious is that if I encapsulate the Integer.parseint to an another method, in my case called parseInt, the toInteger returns the null replacement without throwing the NullPointerException.

I have tried with a shorter expression without nesting conditional operators, and well, a better expression:
    return value == null && isInteger(value) ? parseInt(value) : replace;

That returns the null replacement without the NullPointerException.

I think the bug is nesting conditional operators.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Executing the attached source code is sufficient, is a short source.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When I pass a unparseable string as a first argument, and a null replacement as a second argument,  I expected the toInteger method to return null.
ACTUAL -
The actual result for the toInteger method is a NullPointerException,
but returns the expected null when calling the methods toInteger2, that encapsulates de Integer.parseInt, toInteger3 and toInteger4, that both avoids nested conditional operators.,

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package main;

public class Main {

	public static void main(String[] args) {
		ints();
	}
	
	public static void ints() {
		try {
			Integer i = toInteger(null, null);
			System.out.println(i);
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
			Integer i = toInteger2(null, null);
			System.out.println(i);
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
			Integer i = toInteger3(null, null);
			System.out.println(i);
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
			Integer i = toInteger4(null, null);
			System.out.println(i);
			} catch (Exception e) {
				e.printStackTrace();
			}
	}

	public static Integer toInteger(final String value, final Integer replace) {
		//NullPointerException if replace is null
		return value == null ? replace : isInteger(value) ? Integer.parseInt(value) : replace;
	}

	public static Integer toInteger2(final String value, final Integer replace) {
		//Encapsulates Integer.parseInt with parseInt
		return value == null ? replace : isInteger(value) ? parseInt(value) : replace;
	}

	public static Integer toInteger3(final String value, final Integer replace) {
		return value == null && isInteger(value) ? parseInt(value) : replace;
	}

	public static Integer toInteger4(final String value, final Integer replace) {
		if (value != null && isInteger(value)) {
			return Integer.parseInt(value);
		} else {
			return replace;
		}
	}

	public static boolean isInteger(final String string) {
		return string != null && string.matches("-?\\d+");
	}

	public static Integer parseInt(final String value) {
		return Integer.parseInt(value);
	}

}
---------- END SOURCE ----------


Comments
The code is running afoul of the complicated rules for typing of the ?: operator https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25 The code in the bug has one branch of the ?: typed as an Integer (with the "replace" variable") and the other branch typed as an int from Integer.parseInt. In that case, first an unboxing Integer -> int conversion will occur before a boxing to the final result, leading to the NPE. To avoid this, case the result of parseInt to Integer. Closing as not a bug.
2016-05-26

The problematic part throwing nullpointer exception is : isInteger(value) ? Integer.parseInt(value) : replace The result is the same in JDK 8u66, 8u76 and 9ea.
2016-02-25