Local Type Inference with var
Use var to drop repeated type names on local variables, and know where it is not allowed and where it hides a type you needed to see.
var name = initialiser;
for (var item : items) { ... }
try (var in = open()) { ... }
(var a, var b) -> a + bvar tells the compiler to work out a local variable's type from its initialiser. It arrived in Java 10. The variable is still statically typed: var port = 8080; declares an int, exactly as if you had written int, and it can never hold anything else. var is not dynamic typing.
var headers = new LinkedHashMap<String, List<String>>();
// same as
LinkedHashMap<String, List<String>> headers = new LinkedHashMap<>();Where it works
Local variables with an initialiser, the variable of an enhanced for loop and of a classic for, resources in try-with-resources, and, since Java 11, lambda parameters (useful when you want an annotation on them). It is not allowed for fields, method parameters or return types, so public signatures always spell out their types.
The compiler needs something to infer from, so these do not compile: var x; (no initialiser), var x = null; (no type to infer), var ids = {1, 2}; (an array initialiser needs a declared type) and var f = () -> 1; (a lambda needs a target type).
When it helps and when it hurts
Use it when the type is obvious from the right-hand side: a constructor call, a literal, or a factory such as List.of(...). It removes noise from long generic types, which is common in service code that builds maps of lists.
Avoid it when the right-hand side is a method call whose return type the reader cannot see. var result = repository.find(id); hides whether result is an entity, an Optional or a list, and a reviewer reading the diff has no IDE to hover over. Good variable names matter more with var, not less.
The mistakes people make
Combining var with the diamond operator: var list = new ArrayList<>(); infers ArrayList<Object>, so anything can be added and type checks you expected are gone. Put the type argument on the right: new ArrayList<Long>().
Expecting a wider type: var total = 0; is an int. Later total += 2.5; compiles, because compound assignment casts, and silently truncates. Use var total = 0.0; or declare double. Likewise var infers the concrete class (ArrayList), not the interface (List); that is usually fine for a local, since the variable never leaves the method.
Example
When to use it
- Shorten a local LinkedHashMap<String, List<String>> of request headers built inside a handler.
- Loop over map entries with for (var entry : map.entrySet()) instead of spelling out Map.Entry types.
- Open a reader or connection in try-with-resources where the constructor already names the type.
More examples
var with the diamond operator infers Object
With nothing on either side to name the element type, the compiler falls back to Object. Writing the type argument on the right restores the check.
void main() {
var loose = new ArrayList<>(); // ArrayList<Object>
loose.add(42L);
loose.add("not an id"); // compiles: anything goes
IO.println(loose);
var ids = new ArrayList<Long>(); // ArrayList<Long>
ids.add(42L);
// ids.add("not an id"); // compile error: String cannot be converted to Long
IO.println(ids);
}
// Output:
// [42, not an id]
// [42]The inferred type is fixed, and may be narrower than you think
Each total += 2.5 computes 2.5 or 4.5 and truncates it to an int, giving 4 instead of 5.0. Starting from 0.0 makes the variable a double.
void main() {
var total = 0; // int
total += 2.5; // compiles: compound assignment casts back to int
total += 2.5;
IO.println(total);
var exact = 0.0; // double
exact += 2.5;
exact += 2.5;
IO.println(exact);
// total = "five"; // compile error: var is still statically typed
}
// Output:
// 4
// 5.0var in try-with-resources and lambda parameters
The resource type is clear from the constructor, so var removes repetition without hiding anything. On lambda parameters, var is all-or-nothing and exists mainly so you can add annotations such as a nullness marker.
import java.io.*;
import java.util.function.BinaryOperator;
void main() throws IOException {
try (var reader = new BufferedReader(new StringReader("GET /health\nGET /users"))) {
String line;
while ((line = reader.readLine()) != null) {
IO.println("request: " + line);
}
}
BinaryOperator<Integer> add = (var a, var b) -> a + b; // Java 11+
IO.println(add.apply(2, 3));
}
// Output:
// request: GET /health
// request: GET /users
// 5
Discussion