I ran into a sneaky NullPointerException (NPE) the other day, and it all came down to how Java handles automatic unboxing in ternary expressions.
It started with a line that looked completely innocent. While debugging, I could see that this getter returned null:
devBaseVO.getOnlineStatus() // Evaluates to nullThe moment the ternary expression selected that value, boom—NullPointerException.
What Actually Happened?
Java’s conditional operator has the form condition ? valueIfTrue : valueIfFalse. Before assigning its result, the compiler determines a common type for the second and third operands.
Here is the surprising part: when one operand is a primitive int and the other is an Integer, Java can treat the expression as an int expression. That requires unboxing the Integer. If the selected Integer is null, the generated behavior is effectively a call to .intValue() on null, which immediately throws an NPE.
The type of the variable receiving the result does not prevent this. Even if the result is assigned to an Integer, the unboxing has already happened inside the ternary expression.
Case 1: The Trap—Primitive and Wrapper
import java.util.Objects;
public class HelloDemo { public static void main(String[] args) { User user = new User(); // user.getA() returns null String a = "1"; int c = 1; // Primitive int triggers unboxing of the other operand
Integer b = Objects.equals(a, "2") ? c : user.getA();
System.out.println(b); }
static class User { Integer getA() { return null; } }}The condition is false, so Java evaluates user.getA(). Because the other operand is the primitive int variable c, Java tries to unbox the returned value:
user.getA().intValue()Since user.getA() returns null, the program throws a NullPointerException before the result can be assigned to b.
Case 2: The Fix—Wrapper and Wrapper
import java.util.Objects;
public class HelloDemo { public static void main(String[] args) { User user = new User(); // user.getA() returns null String a = "1"; Integer c = 1; // Both operands are Integer references
Integer b = Objects.equals(a, "2") ? c : user.getA();
System.out.println(b); }
static class User { Integer getA() { return null; } }}This time, both possible values are Integer references. The expression can therefore produce an Integer without unboxing the selected value.
Output:
nullAnother Safe Option: Handle Null Explicitly
Changing both operands to wrapper types is useful when null is a valid result. If the rest of the code expects a primitive, however, it is usually clearer to choose an explicit fallback:
Integer onlineStatus = devBaseVO.getOnlineStatus();int status = condition ? 1 : Objects.requireNonNullElse(onlineStatus, 0);This makes the intended behavior visible: a missing online status becomes 0. If null indicates invalid state instead, fail with a more descriptive message:
int status = condition ? 1 : Objects.requireNonNull( devBaseVO.getOnlineStatus(), "Online status must not be null" );The Key Takeaway
When a ternary expression mixes a primitive with its wrapper type, do not assume the wrapper can safely remain null. Check the type Java gives the entire expression and remember that unboxing occurs before assignment.
To avoid this trap:
- Use wrapper types on both sides when
nullis a valid result. - Provide an explicit default when the result must be a primitive.
- Validate nullable values before using them in mixed primitive-wrapper expressions.
A tiny type difference—int versus Integer—can be the difference between printing null and throwing an exception.