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.

Syntaxvar name = initialiser; for (var item : items) { ... } try (var in = open()) { ... } (var a, var b) -> a + b

var 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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.

Example · java
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.0

var 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.

Example · java
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

  • Be the first to comment on this lesson.