skip to content

How does Java determine the result type of a ternary when the two branches are different numeric types?

level: middleimportance: should knowfreq 55%

answer

  1. One expression → one compile-time type from BOTH branches
  2. Numeric branches → binary numeric promotion (widen to wider)
  3. true ? 1 : 2.0 == 1.0 (not 1)
  4. Order of widening: int < long < float < double
  5. Constant-fits-narrow keeps byte/short/char

basics

~20 s

Java picks one common type for the whole expression. With two different number types it usually promotes both to the wider one (for example int and double become double), so the ternary's result is that wider type — even on the branch that wasn't taken.

solid answer

~50 s

A ternary produces a single value, so the compiler computes one result type from the two branches at compile time. When both branches are numeric, Java applies the conditional-expression typing rules, which for the common case mean **binary numeric promotion**: both operands are promoted to the wider of the two types and the whole expression has that type. So `cond ? 1 : 2.0` is `double` (the int 1 becomes 1.0), and `cond ? anInt : aLong` is `long`. There are special cases: if one branch is a constant that fits in the other's smaller type (e.g. byte/short/char), the result can stay narrow. The practical takeaway is that the result type is decided by *both* branches together, not by whichever branch happens to run — which can silently widen your value, e.g. `true ? 1 : 2.0` yields `1.0`, not `1`.

code

java · 12 lines
java
boolean cond = true;

System.out.println(cond ? 1 : 2.0);   // 1.0  -> result type is double

int  i = 1;
long L = 5L;
var  x = cond ? i : L;                // x is long (i widened to long)

// assigning back to a narrow type needs the result type to fit:
short s = 4;
// short out = cond ? s : 100000;     // would NOT compile: 100000 doesn't fit short
short ok  = cond ? s : 5;             // compiles: 5 is a constant that fits short

go deeper

for a junior

Knows mixing an int and a double in a ternary gives a double; may not recall all promotion ranks.

for a middle

Explains binary numeric promotion picks the wider type and that the type is fixed at compile time from both branches, with examples like true ? 1 : 2.0 == 1.0.

for a senior

Knows the narrow-type constant-fits exception for byte/short/char and that promotion can trigger unboxing/NPE; advises keeping branches the same type.

for a principal

Can cite the JLS conditional-expression typing structure, anticipate cross-team bugs from silent widening, and codify guidance (matching literals, explicit casts) in standards.

## The core idea The ternary operator is an **expression**, so the whole thing has exactly **one type**, computed by the compiler from the two branches *before* the program runs. The condition only decides which *value* you get at runtime; it does **not** decide the type. This is the source of a lot of surprises. ## Background terms - **Primitive numeric types**, from narrow to wide: `byte` (8-bit) → `short`/`char` (16-bit) → `int` (32-bit) → `long` (64-bit) → `float` (32-bit floating) → `double` (64-bit floating). - **Widening**: converting a narrower type to a wider one (e.g. `int` → `double`), which never loses range (though `long`→`float`/`double` can lose precision). - **Binary numeric promotion**: Java's rule for binary numeric operators. Given two numeric operands, both are converted to a common type: if either is `double`, both become `double`; else if either is `float`, both become `float`; else if either is `long`, both become `long`; otherwise both become `int`. (`byte`/`short`/`char` are always promoted at least to `int`.) ## How the ternary applies it For the common case where both branches are numeric, the conditional expression's type is the result of **binary numeric promotion** of the two branch types: ```java int i = 1; double d = 2.0; var r = cond ? i : d; // r is double; the int branch is widened to double ``` Even if `cond` is `true` and the chosen branch is the `int` `1`, the value is widened to `1.0` because the **expression's type is `double`**: ```java System.out.println(true ? 1 : 2.0); // prints 1.0, NOT 1 ``` Another classic: ```java long L = 5L; int n = 3; var x = cond ? n : L; // x is long ``` ## The special narrow-type cases The Java Language Specification adds rules so small types are not always blown up to `int`: - If both branches have the **same** type, that is the result type. - If one branch is `byte`/`short`/`char` and the other is a **constant expression of type `int`** whose value **fits** in the narrow type, the result keeps the narrow type. For example `cond ? aShort : 5` can be `short` if `5` fits in a `short`. - Mixing in any `double`/`float`/`long` triggers the corresponding promotion as above. These narrow-type rules matter when you assign the result back to a small type without a cast. ## Why you should care 1. **Silent widening of literals** — `flag ? 0 : 1.5` is `double`, so even the `0` path gives `0.0`. If you assign to an `int`, it won't compile without a cast; if you print or compare, the value type may surprise you. 2. **It interacts with autoboxing** — when one branch is a primitive and the other a wrapper, numeric promotion can force **auto-unboxing** of the wrapper, which throws `NullPointerException` if the wrapper is `null`. (Covered fully in the boxing/unboxing question.) 3. **Readability** — if you depend on a specific type, make it explicit with a cast or matching literals rather than relying on these rules. ## Rule of thumb Keep both branches the **same** type. If they differ on purpose, write the cast or the literal form (`1.0` vs `1`) so the result type is obvious to the next reader and to you.

  • Why does System.out.println(true ? 1 : 2.0) print 1.0 instead of 1?
    The result type of the ternary is computed from both branches: int 1 and double 2.0 promote to double, so the whole expression is double. The chosen int value 1 is widened to 1.0 regardless of the condition.
  • What is the result type of cond ? anInt : aLong?
    long. Binary numeric promotion takes the wider of int and long, so the int branch is widened and the expression's type is long.

Imagine two pipes of different diameters feeding one outlet. The outlet must fit the wider pipe, so even water coming from the narrow pipe gets routed through the wide fitting. The ternary's result type is that wide fitting — both branches must conform to it.

saying these in an interview costs you the question

  • Believing the result type depends on which branch is taken at runtime
  • Assuming cond ? 1 : 2.0 yields an int
  • Forgetting byte/short/char are normally promoted to int
  • Ignoring that promotion can force unboxing and an NPE

context