Part 4 of 16

var Keyword (JEP 286, 323): Local Variable Type Inference

What var Does

var is a reserved type name (not a keyword) introduced in Java 10 (JEP 286). It instructs the compiler to infer the type of a local variable from its initialiser. The inferred type is fixed at compile time — var does not make Java dynamically typed.

// Before var
ArrayList<String> names = new ArrayList<String>();
Map<String, List<Integer>> scores = new HashMap<String, List<Integer>>();

// With var
var names = new ArrayList<String>();
var scores = new HashMap<String, List<Integer>>();

After compilation, both forms produce identical bytecode. The inferred type is embedded in the class file.


How Type Inference Works

The compiler determines the type from the right-hand side of the initialiser expression:

var x = 42;          // int
var d = 3.14;        // double
var s = "hello";     // String
var list = List.of(1, 2, 3);   // List<Integer>
var map = new HashMap<String, Integer>();  // HashMap<String, Integer>

The inferred type is the static type of the initialiser expression — the most specific type the compiler can determine without losing information:

var obj = new ArrayList<String>();   // inferred: ArrayList<String>, NOT List<String> or Object

This distinction matters:

var list = new ArrayList<String>();
list.trimToSize();   // OK — trimToSize() is an ArrayList method, not a List method

If you had used List<String>, the call to trimToSize() would not compile. var gives you the more specific type automatically.


Where var Is Allowed

Local variables with initialisers

var count = 0;
var message = "Hello";
var client = HttpClient.newHttpClient();

For-each loops

var names = List.of("Alice", "Bob", "Charlie");
for (var name : names) {
    System.out.println(name.toUpperCase());  // name is String
}

Traditional for loops

for (var i = 0; i < 10; i++) {
    System.out.println(i);
}

try-with-resources

try (var reader = new BufferedReader(new FileReader("data.txt"))) {
    var line = reader.readLine();
    System.out.println(line);
}

Where var Is NOT Allowed

Field declarations

// Compile error — var cannot be used for class fields
class Config {
    var timeout = 30;  // ERROR
}

Method parameters

// Compile error
void process(var data) { }  // ERROR

Method return types

// Compile error
var getResult() { return "result"; }  // ERROR

Null initialisers (type cannot be inferred)

var x = null;  // ERROR: cannot infer type from null

Array initialisers without type

var arr = {1, 2, 3};       // ERROR: array initialiser requires explicit type
var arr = new int[]{1, 2, 3};  // OK

Lambda without target type

var fn = x -> x * 2;  // ERROR: lambda requires target type

JEP 323: var in Lambda Parameters (Java 11)

Java 11 (JEP 323) extends var to lambda parameter declarations. This is primarily useful for adding annotations to lambda parameters, which requires explicit type syntax.

// Without JEP 323 — you had to choose: implicit types or explicit types
(String s) -> s.toUpperCase()   // explicit type, allows annotation
s -> s.toUpperCase()             // implicit type, no annotation

// With JEP 323 — var in lambda parameters allows annotations without explicit types
(@NonNull var s) -> s.toUpperCase()        // annotated, no explicit type
(@NotNull var x, @NotNull var y) -> x + y  // both params annotated

All lambda parameter declarations must be consistent — either all use var, all use explicit types, or all are implicit:

// OK — all var
(var x, var y) -> x + y

// OK — all explicit
(int x, int y) -> x + y

// ERROR — mixed
(var x, y) -> x + y        // cannot mix var and implicit
(var x, int y) -> x + y    // cannot mix var and explicit

Practical Examples

Streams and pipelines

var files = Files.list(Path.of("/tmp"))
    .filter(Files::isRegularFile)
    .collect(Collectors.toList());

for (var file : files) {
    var size = Files.size(file);
    System.out.printf("%s: %d bytes%n", file.getFileName(), size);
}

Working with complex generic types

// Without var — repetitive generic type
Map<String, Map<Integer, List<String>>> registry =
    new HashMap<String, Map<Integer, List<String>>>();

// With var — type still inferred correctly from the right side
var registry = new HashMap<String, Map<Integer, List<String>>>();

try-with-resources chaining

try (var conn = dataSource.getConnection();
     var stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
    stmt.setInt(1, userId);
    try (var rs = stmt.executeQuery()) {
        while (rs.next()) {
            var name = rs.getString("name");
            System.out.println(name);
        }
    }
}

Annotated lambda parameters (JEP 323 use case)

// Annotation-based null checking in lambda parameters
List<String> result = names.stream()
    .map((@NonNull var s) -> s.toLowerCase())
    .collect(Collectors.toList());

Style Guidelines

var improves readability when the type is obvious from the right-hand side. It hurts readability when the type is non-obvious and the name alone does not compensate.

Good uses of var

Right-hand side makes the type obvious:

var client = HttpClient.newHttpClient();         // obviously HttpClient
var users = new ArrayList<User>();              // obviously ArrayList<User>
var reader = new BufferedReader(new FileReader(path)); // obviously BufferedReader

Reduces redundancy in generic types:

var entries = new HashMap<String, List<Order>>();  // type visible on right

Loop variables where the collection type documents itself:

for (var entry : map.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

Avoid var when

The type is not obvious from the right-hand side:

// Bad — what type does getResult() return?
var result = service.getResult();

// Better — name the type explicitly
ProcessResult result = service.getResult();

The name alone does not document the type:

// Bad — what is data?
var data = process(input);

// Better
ByteBuffer data = process(input);

The scope is long and the type matters for reading the code:

// In a 50-line method, a reader should not have to scroll back to find the type
var thing = buildComplexObject();   // confusing across long scope

var Is Not Dynamic Typing

A common misconception: var looks like JavaScript’s var but it is completely different. Java’s var is statically inferred at compile time. Reassignment cannot change the inferred type:

var x = 10;       // inferred: int
x = "hello";      // COMPILE ERROR: incompatible types: String cannot be converted to int

The bytecode is identical to using the explicit type. There is zero runtime overhead.


var and Generics

var infers the full generic type:

var list = new ArrayList<String>();
list.add("hello");
list.add(42);    // COMPILE ERROR: int cannot be added to ArrayList<String>

But with diamond and empty collection literals, inference needs a hint:

var list = new ArrayList<>();   // inferred: ArrayList<Object> — probably not what you want
// Better: be explicit about the element type
var list = new ArrayList<String>();

Decompiled Bytecode

The compiler produces identical bytecode whether you use var or the explicit type:

// Source
var x = 42;

// Bytecode (javap -c)
// bipush 42
// istore_1
// identical to: int x = 42;

What’s Next

Next: New String Methods (Java 11): isBlank, lines, strip, repeat