Exactly what static type does the compiler infer for a `var`? Walk through a few tricky cases.
answer
- Inferred = static type of the initializer
- Integer literal → int (no byte/short widening)
- Bare diamond with var → <Object>; always write the type arg
- var can capture non-denotable types (anonymous class, intersection)
- Ternary → least common supertype of branches
basics
~20 sThe compiler infers the type of the initializer expression. So var x = 5 is int, var s = "hi" is String, and var d = 3.0 is double. Whatever the right-hand side's type is, that becomes the variable's fixed type.
solid answer
~50 sThe inferred type is the **static type of the initializer expression** — not necessarily what you might expect. `var i = 0` is `int`; `var d = 1.0` is `double`; `var c = 'a'` is `char`. A subtle case: numeric literals are NOT widened, so `var b = 0` gives `int`, never `byte`. The diamond operator combines with `var` to fall back to the bound's type: `var list = new ArrayList<>()` infers `ArrayList<Object>`, so always write the type argument (`new ArrayList<String>()`). The inferred type can also be a **non-denotable** type you can't write by hand — e.g. an intersection type or the type of an anonymous class — which means `var` can capture types you otherwise couldn't name. The type is fixed at declaration and statically checked from then on. Conditional/ternary expressions infer the common supertype of both branches.
go deeper
Knows the inferred type is whatever the right-hand value's type is (var x = 5 is an int).
Can spot the diamond pitfall (new ArrayList<>() becomes <Object>) and that integer literals infer int, not narrower types.
Explains inference precisely (static type of the initializer), ternary common-supertype behavior, and non-denotable types like anonymous-class types.
Reasons about denotable vs non-denotable types as a language concept, the readability/maintainability trade-offs, and sets team conventions (explicit type args, avoid hiding return types).
## The core rule The compiler infers **the static type of the initializer expression**, then treats the variable as if you had written that type explicitly. Two things matter: (a) what the initializer's type actually is, and (b) that the result must be a real type the variable can hold. ## Primitive literals — no surprise widening The inferred type matches the literal's own type: ```java var i = 0; // int (NOT byte/short) var l = 0L; // long var d = 1.0; // double var f = 1.0f; // float var c = 'a'; // char var b = true; // boolean ``` A classic gotcha: people expect `var x = 0` might become `byte`. It does not — an untyped integer literal is `int`. If you need `byte`, you cannot use `var` (`byte b = 0;` relies on the explicit type to narrow the literal). ## Diamond operator pitfall The **diamond** `<>` normally infers type arguments from the *target* type on the left: ```java List<String> xs = new ArrayList<>(); // <> infers String from the left ``` But with `var` there is no left-hand type, so the diamond has nothing to infer from and falls back to the bound — `Object`: ```java var xs = new ArrayList<>(); // inferred ArrayList<Object> — usually NOT what you want ``` **Fix:** always give the type argument explicitly with `var`: ```java var xs = new ArrayList<String>(); // ArrayList<String> ``` ## Ternary / conditional expressions The inferred type is the type of the conditional expression, which is the **least common supertype** of the two branches: ```java var x = cond ? 1 : 2; // int var y = cond ? "a" : new StringBuilder(); // a complex common supertype (CharSequence & Serializable & ...) ``` ## Non-denotable types — a unique power of `var` A **denotable** type is one you can write by hand in source code. Some expression types are **non-denotable** — you literally cannot name them: - The type of an **anonymous class** instance: ```java var obj = new Object() { int field = 42; }; obj.field; // works! With var you can access members of the anonymous type ``` Had you written `Object obj = ...`, `obj.field` would not compile. `var` captures the anonymous class's own type, which has no name. - **Intersection types** from casts or inference (`var x = (CharSequence & Serializable) something;`). This is a genuine capability `var` adds: it can hold types you cannot otherwise spell out. Used sparingly (the scope is one method), it is occasionally useful. ## The type is fixed and statically enforced Whatever is inferred, it is final for that variable. All later uses are type-checked against it exactly as if you had written the type. There is no widening of the variable on later assignment. ## Practical guidance - Prefer `var` when the initializer makes the type obvious (`var user = new User()`), avoid it when the right-hand side hides the type (`var x = process()` — what does it return?). - Always supply explicit type arguments with `var` (no bare diamond). - Be aware factory-style returns (`List.of(...)`) infer the declared return type, e.g. `var l = List.of(1, 2)` is `List<Integer>`.
- What type is inferred for `var x = new ArrayList<>();` and why is it a problem?`ArrayList<Object>`. With `var` there is no left-hand target for the diamond to infer from, so it falls back to `Object`. Always write the explicit type argument: `new ArrayList<String>()`.
- Can `var` infer a type you cannot write by hand?Yes — non-denotable types like an anonymous class's own type or an intersection type. That lets you, e.g., access fields declared in an anonymous class, which an explicit `Object` declaration would hide.
saying these in an interview costs you the question
- Thinking `var b = 0` infers `byte`
- Expecting `var x = new ArrayList<>()` to be `ArrayList<String>`
- Believing `var` always infers an interface/supertype rather than the concrete initializer type
- Not knowing `var` can access members of an anonymous class instance