Java var Keyword (Local-Variable Type Inference)
Use var for local variable type inference in Java, when it improves readability, and when it doesn't.
Java var Keyword (Local-Variable Type Inference)
Since Java 10, you can declare a local variable with var and let the compiler infer its type from the initializer. var greeting = "hello"; is exactly the same, compiled bytecode and all, as String greeting = "hello"; — the type is still String, you simply didn't type it twice. This is local-variable type inference: a syntactic convenience that trims redundant type names without making Java dynamically typed. Used well it removes noise; used carelessly it hides the very information a reader needs.
var is inference, not dynamic typing
The single most important fact: var is not a new "anything" type. The compiler reads the right-hand side, works out the static type, and bakes it in. From that point on the variable is as strongly typed as if you had written the type by hand — you cannot reassign it to an unrelated type, and the inferred type is fixed at compile time.
var name = "Ada"; // name has static type String, forever
name = "Lovelace"; // fine, still a String
name = 42; // compile error: int cannot be assigned to Stringvar is reserved type name, not a keyword — you can still use var as a variable or method name (though you shouldn't). It only triggers inference in the local-variable declaration position.
Where var is allowed — and where it isn't
var works only for local variables that have an initializer. The compiler needs a right-hand side to read the type from; without one there is nothing to infer.
| Position | var allowed? | Reason |
|---|---|---|
| Local variable with initializer | Yes | The initializer supplies the type |
Index/element in for loops | Yes | The loop expression supplies the type |
| Try-with-resources variable | Yes | The resource expression supplies the type |
| Local variable without initializer | No | Nothing to infer from |
| Fields / instance variables | No | Inference is local-only by design |
| Method parameters | No | Callers, not initializers, supply values |
| Method return types | No | Same reason as parameters |
Initialized to null only | No | null has no concrete type |
| Lambda parameters (bare) | Special | (var x, var y) -> ... is allowed since Java 11 |
var x; // error: cannot infer type, no initializer
var nothing = null; // error: null has no type to infer
public var field = 1; // error: var not allowed on fields
void m(var p) { } // error: var not allowed on parametersThe real payoff: collapsing noisy generics
var earns its keep when the type name is long, repeated, or generic-heavy. The classic case is a declaration where the type appears in full on both sides of the =:
// Before: the type name is written twice
Map<String, List<Customer>> byCity = new HashMap<String, List<Customer>>();
// After: the right side already says everything
var byCity = new HashMap<String, List<Customer>>();It also shines with iterators, Map.Entry, and other verbose types that add no clarity when spelled out:
for (var entry : byCity.entrySet()) { // Map.Entry<String, List<Customer>>
System.out.println(entry.getKey() + " -> " + entry.getValue().size());
}When NOT to use var
var helps when the type is obvious from the right-hand side and hurts when it isn't. If a reader has to run the code in their head to know the type, write the type out.
var result = service.process(input); // unclear: what does process return?
Order result = service.process(input); // clear: an Order
var flag = true; // fine, obviously boolean
var count = list.size(); // fine, obviously intWatch the numeric literal trap: var infers the literal's type, not the type you may have meant.
var n = 100; // int, not long — for a long you must write 100L or long n
var f = 3.14; // double, not float
byte b = 1; // explicit type narrows; var b = 1 would be intAvoid var when it loses a deliberate interface type. var list = new ArrayList<String>(); types list as ArrayList<String>, not List<String> — fine locally, but if you meant to program to the interface, say so.
A worked example you can run
This program exercises var across all its legal positions — simple values, a generic map, an enhanced for-loop, an indexed for-loop — and uses getClass().getSimpleName() to prove the inferred runtime types are exactly what the right-hand sides implied.
What to take from the run:
greeting.getClass().getSimpleName()printsString, provingvar greeting = "hello"produced a genuineString—varis compile-time inference, and at runtime the object is exactly what the literal implied, nothing dynamic about it.count + 1 = 43andprice * 2 = 19.98confirm the numeric inference rules:42madecountanint,9.99madepriceadouble. The literal's type — not your intent — decides, which is the trap to remember when you need alongorfloat.scores type = HashMapshowsvarcaptured the concrete right-hand-side typeHashMap, not theMapinterface; the diamond<String, List<Integer>>on the right gave the compiler everything it needed even though the left side said onlyvar.total chars = 10comes fromfor (var name : names)wherenamewas inferred asString, soname.length()resolved correctly (3 + 3 + 4) —varworks in enhanced for-loops, inferring the element type from the iterable.0..4 sum = 10comes fromfor (var i = 0; ...)whereiwas inferred asintfrom the literal0; the indexed loop is one of the cleanest places to usevarbecause the type is unmistakable.
Practice
In which of these declarations is 'var' legal and does it infer the type the comment claims?