I think the answer can be found in chapter 15.25 of the JLS
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
So when either the second or third operand is a primitive type the expression's type is a primitive. Thus if you pass a null
reference the branch : modelYear
will be executed. But since one operand is primitive it must be unboxed. This causes the NPE.
You can also see this if you take a look at the generated byte code
private convertTo4Digits(Ljava/lang/Integer;)Ljava/lang/Integer;
L0
LINENUMBER 46 L0
ALOAD 1
IFNULL L1
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue()I
BIPUSH 100
IF_ICMPGE L1
SIPUSH 2000
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue()I
IADD
GOTO L2
L1
LINENUMBER 47 L1
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue()I
L2
LINENUMBER 46 L2
INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
ARETURN
Your own answer solves the problem because you are casting the second operand to Integer
(modelYear != null && modelYear < 100) ? (Integer) (2000 + modelYear) : modelYear;
and therefore neither the second nor the thrid operand are of primitive type. Thus the rule of the JLS I posted above is not applied and the NPE is gone.