skip to content

var

local type inference: what it infers, and where it isn't allowed.

chapter
chapter 02.13
reading time
7 min read

var lets the compiler infer a local variable's type from its initializer, so you don't write it twice. the type is still decided at compile time and never changes: java stays statically typed. it only works for local variables that have an initializer, never for fields, parameters or return types.

the picture

java
Map<String, List<Integer>> scores = new HashMap<String, List<Integer>>();

that line says the same thing twice. you know how filling in a form asks for your date of birth and then your age? kinda redundant. var is java noticing the right-hand side already says what the type is (JEP 286, java 10):

java
var scores = new HashMap<String, List<Integer>>();

the compiler reads the initializer, works out its type, and writes that type into the variable exactly as if you had typed it. it happens entirely at compile time.

hover a stage.

hover a stage. var lives and dies inside javac. checked on jdk 25: the .class file compiled from the var version and from the explicit version are byte-for-byte identical (cmp finds no difference).

what it infers

the inferred type is the exact static type of the expression on the right. usually that's what you'd have written. sometimes it isn't.

javac infers
ArrayList<String>

the exact class on the right, ArrayList, not the List interface you might have written by hand.

hover or tap a line to see the type javac infers. every type here is what jshell 25's /vars printed for that exact declaration. the amber ones surprise people; the red ones are traps.

two to remember. var list = new ArrayList<String>() gives you an ArrayList, not a List: fine for a local, but it's a different type than you'd usually declare. and var empty = new ArrayList<>() gives you ArrayList<Object>, because the diamond had nothing to infer from. pair var with an explicit type argument, never with an empty diamond.

Main.java
void main() {
    var names = new ArrayList<String>();
    names.add("ada");
    var anon = new Object() { int hits = 3; };
    IO.println(names.get(0).toUpperCase() + " " + anon.hits);
}
output
ADA 3

names.get(0) is a String (so toUpperCase compiles), and anon.hits works because var kept the anonymous class's own type, one you couldn't have written by hand.

it's not dynamic typing

var is not any, and it's not javascript's let. once inferred, the type is locked:

predict the output
java
var x = 5;
x = "five";
IO.println(x);

where it's allowed

only where there's a local variable and an initializer to infer from. everywhere else, the type is part of a contract (fields, parameters, return types) or there's nothing to look at.

void f(var x) { }
error: 'var' is not allowed here

there's nothing to infer from: a parameter has no initializer, its value comes from callers.

pick a spot. the ✓ ones compile; the ✗ ones show javac 25's actual error. most of the "cannot infer" errors come down to the same thing: the right side doesn't have a type of its own yet.

lambda parameters joined in java 11 (JEP 323). (var x, var y) -> x + y means exactly the same as (x, y) -> x + y. the reason it exists: you can put an annotation on a var parameter, like (@NonNull var x, var y) -> ..., which you can't do with a bare name.

when to use it

var is about readability. the rule of thumb from the OpenJDK style notes boils down to: use it when the reader loses nothing.

don'tthe right side doesn't say what it is
java
var result = service.process(order);
var x = compute();
for (var e : lookup()) { ... }

now the reader has to go find process to know what result even is.

dothe type is right there anyway
java
var orders = new ArrayList<Order>();
var path = Path.of("data", "orders.csv");
var count = 0;
try (var in = Files.newBufferedReader(path)) { ... }

the constructor, factory method or literal already names the type.

good variable names matter more with var, not less. var orders tells you more than List<Order> list ever did.

one level deeper:why you can still name a variable var+

var isn't a keyword. it's a reserved type name: it means something special only where a type is expected. so var var = "var"; compiles and prints var, and old code with variables called var kept working in java 10. what you can't do anymore is name a class var (error: 'var' not allowed here), since that would make var x = ... ambiguous. yield, record, sealed and permits were added later in the same careful way (special only where they mean something), for the same reason: never break existing code.

keylines
  • var infers a local variable's type from its initializer, at compile time.
  • the type is fixed. var x = 5; x = "five"; doesn't compile.
  • it infers the exact type on the right: ArrayList<String>, not List<String>. an empty diamond gives Object.
  • locals only, with an initializer. no fields, parameters, return types, null, lambdas or bare {1, 2}.
  • use it when the right side already says the type. otherwise, write it out.