The Java ternary operator with autoboxing can produce surprising results when type promotion rules apply.

A quick puzzle

Take a look at this code:

public class Main {
    public static void main(String[] args) {
        Object o = true ? Integer.valueOf(1) : Double.valueOf(2.0);
        System.out.println(o.getClass().getName() + " -> " + o);
    }
}

Question:
What will this program print?

Did you expect:

java.lang.Integer -> 1

…but the actual output is:

java.lang.Double -> 1.0

Surprised? Let’s unpack why.

TL;DR

  • In a ternary expression, if the two branches are boxed numeric types (e.g. Integer and Double), the compiler may unbox, promote, and rebox them.
  • In this example, Integer gets unboxed to int, promoted to double (since double wins over int in numeric promotion), and then reboxed into a Double.
  • Result: you always get a Double, even if the chosen branch was originally an Integer.

The rules (JLS references)

The magic comes from the Java Language Specification:

  1. Conditional Operator (§15.25)https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25
    • The result type of a ? b : c depends on the types of b and c.
    • If both are numeric, Java may apply unboxing and numeric promotion rules to determine the common type.
  2. Boxing & Unboxing (§5.1.7, §5.1.8)
    • Integer unboxes to int, Double unboxes to double.
  3. Numeric Promotions (§5.6.2)
    • When combining int and double, the int is promoted to double.
  4. Boxing (§5.1.7)
    • The resulting double is boxed into a Double.

More Examples

Example 1: Integer vs Double

Object o = false ? Integer.valueOf(1) : Double.valueOf(2.0);
System.out.println(o.getClass().getName() + " -> " + o);

Output:

java.lang.Double -> 2.0

Even when the Integer branch is never taken, the type is resolved at compile time, so the whole expression is a Double.

Example 2: Casting to a common supertype

Number n = true ? (Number) Integer.valueOf(1) : Double.valueOf(2.0);
System.out.println(n.getClass().getName() + " -> " + n);

Output:

java.lang.Integer -> 1

Why? Because both branches are now typed as Number. No unboxing/promotion is needed, so the chosen branch’s type is preserved.

Example 3: Primitives + Wrappers

Object o = true ? 1 : Double.valueOf(2.0);
System.out.println(o.getClass().getName() + " -> " + o);

Output:

java.lang.Double -> 1.0

Here, 1 is a primitive int. The compiler promotes it to double, then boxes into Double.

Example 4: Float and Integer

Object o = true ? Float.valueOf(1.0f) : Integer.valueOf(42);
System.out.println(o.getClass().getName() + " -> " + o);

Output:

java.lang.Float -> 1.0

Again, both are unboxed → numeric promotion (int → float) → reboxed to Float.

Key Takeaways

  1. Ternary + Boxed Numerics = Hidden Conversions
    • If branches are Integer, Double, Float, etc., Java often unboxes, promotes, and reboxes.
  2. If you want to preserve wrapper types:
    • Cast both branches to a common supertype (Number, Object)
    • Or avoid mixing different boxed numerics.
  3. Readability matters.
    • If the type coercion isn’t obvious to future readers, use an explicit if/else for clarity.

This flow shows the decision process: same wrapper → keep it, different wrappers → unbox, promote, then box again.

Closing

The ternary operator is elegant, but in Java, it can hide subtle coercions when autoboxing and numeric promotion get involved. Knowing the JLS rules behind it helps avoid surprises — and helps you explain to your teammates why Integer.valueOf(1) suddenly became a Double.