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.
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.
This page covers what var does and doesn't do, exactly where it's legal, the cases where it pays off, the cases where it hurts readability, and a runnable program that proves the inferred types are what you expect.
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, the Map.Entry you get from a HashMap, 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
Related topics
- Java Variables — declaring and initializing local variables.
- Variable Scope — why
varis intentionally limited to local scope. - Generics — the verbose type names
varis best at hiding. - The
forLoop — wherevar iandvar entryread cleanly.