Java var: Local Variable Type Inference (Java 10+)

var, introduced in Java 10 (JEP 286), lets the compiler infer the type of a local variable from its initializer: you write var instead of the explicit type. It is not dynamic typing: the variable still has a strong static type at compile time; you just don't have to write it out. Used well, var reduces boilerplate for verbose generic types. Used carelessly, it hurts readability by hiding the type.

Contents

Basic Usage

var replaces the explicit type in a local variable declaration. The type is still determined at compile time from the right-hand-side initializer: var is syntactic sugar, not dynamic typing:

Java
// Without var — explicit type
String message = "Hello, World!";
int count = 42;
List<String> names = new ArrayList<>();

// With var — type inferred from right-hand side
var message2 = "Hello, World!";   // inferred as String
var count2   = 42;                 // inferred as int
var names2   = new ArrayList<String>(); // inferred as ArrayList<String>

// var is resolved at compile time — it is NOT dynamic typing
var x = 10;
// x = "hello"; // COMPILE ERROR — x is int, not Object

// The actual type is the static type of the initializer expression
var list = List.of(1, 2, 3); // type is List<Integer> (not ArrayList)
var map  = new HashMap<String, List<Integer>>(); // HashMap<String,List<Integer>>

// var for try-with-resources (Java 9+ effectively-final feature)
try (var stream = Files.lines(Path.of("data.txt"))) {
    stream.forEach(System.out::println);
}

Where var Is Allowed

var is restricted to local variable contexts where the initializer is present and unambiguous. It works in regular local declarations, for loop indexes, enhanced for-each variables, and try-with-resources variables:

Java
// 1. Local variable declarations with an initializer
var s = "hello";                       // ✓
var n = 42;                            // ✓
var map = new HashMap<String, Integer>(); // ✓

// 2. Index variable in a for loop
for (var i = 0; i < 10; i++) { }      // ✓

// 3. Enhanced for-each loop variable
List<String> items = List.of("a", "b", "c");
for (var item : items) {               // ✓
    System.out.println(item.toUpperCase()); // knows item is String
}

// 4. try-with-resources variable
try (var conn = getConnection()) { }   // ✓

// 5. Local variable in a lambda (Java 11+) — allows adding annotations
// (@NotNull var s) -> s.toUpperCase()  ← annotation on lambda parameter
// Without annotation, just use normal lambda syntax

Where var Is NOT Allowed

var cannot be used anywhere the compiler cannot infer the type from a local initializer. Common invalid uses include fields, method parameters, return types, declarations without an initializer, and lambdas or method references without a target type:

Java
// 1. Fields (instance or static)
// class Foo { var x = 10; }        // COMPILE ERROR

// 2. Method parameters
// void process(var item) { }       // COMPILE ERROR

// 3. Method return types
// var compute() { return 42; }     // COMPILE ERROR

// 4. No initializer
// var x;                           // COMPILE ERROR — can't infer without initializer

// 5. Initializer is null — type cannot be inferred
// var x = null;                    // COMPILE ERROR

// 6. Array initializer shorthand
// var arr = {1, 2, 3};             // COMPILE ERROR
var arr = new int[]{1, 2, 3};       // ✓ — full initializer works

// 7. Lambda without target type
// var f = () -> 42;               // COMPILE ERROR — lambda needs target type
// Use explicit type:
Supplier<Integer> f = () -> 42;    // ✓

// 8. Method reference without target type
// var ref = String::toUpperCase;   // COMPILE ERROR
Function<String, String> upper = String::toUpperCase; // ✓

var with Generics and Collections

var is most valuable with deeply nested generic types where repeating the full type declaration adds noise without clarity. The inferred type is the static type of the initializer expression, not necessarily the interface you would have declared explicitly:

Java
// var shines with complex generic types — reduces verbosity
// Before Java 10:
Map<String, List<Map<Integer, Set<String>>>> complex =
    new HashMap<String, List<Map<Integer, Set<String>>>>();

// With var:
var complex = new HashMap<String, List<Map<Integer, Set<String>>>>();

// Iterator pattern — no need to repeat the type
var iterator = someCollection.iterator();
while (iterator.hasNext()) {
    var element = iterator.next(); // type inferred from iterator
    // ...
}

// Entry sets
var entries = new HashMap<String, Integer>().entrySet();
for (var entry : entries) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

// With streams — var can hold the intermediate stream type
var stream = IntStream.range(0, 100)
                      .filter(n -> n % 2 == 0)
                      .mapToObj(Integer::toString);
// stream is Stream<String>

// Caution: var with diamond operator gives the raw type
var list = new ArrayList<>(); // ArrayList<Object> — not what you want
var list2 = new ArrayList<String>(); // ArrayList<String> — explicit type argument needed
Using var with the diamond operator new ArrayList<>() gives you ArrayList<Object> because there is no context to infer the element type. Always provide the type argument: new ArrayList<String>().

Best Practices

Use var when the type is immediately obvious from the right-hand side (constructor calls, factory methods with a clear return type). Avoid it when the type is what gives the variable its meaning, especially when calling methods whose return type is not evident from the name alone:

Java
// ✓ GOOD — var reduces noise when type is obvious from right side
var path    = Path.of("/home/user/data.txt");  // clearly a Path
var entries = map.entrySet();                  // clearly Map.Entry set
var conn    = dataSource.getConnection();       // clearly a Connection

// ✓ GOOD — var avoids repeating long generic type names
var registry = new HashMap<String, List<EventHandler<UserEvent>>>();

// ✗ BAD — type is not clear from the right side
var result = process();   // what type is result? Forces reader to look up process()
var data   = repository.findById(id); // Optional<User>? User? Object?

// ✗ BAD — primitive widening can surprise
var x = 1;           // int, not long!
long expected = x;   // fine, widening
// If you need long: long x = 1L; or var x = 1L;

// ✗ BAD — in public API documentation
// Method bodies using var are fine; parameters and return types must be explicit

// Rule of thumb:
// Use var when the type is clear and unambiguous from the right-hand side.
// Avoid var when the type adds meaningful context that aids understanding.
var is a special identifier, not a keyword: you can still use var as a variable name, class name, or method name in older code without breaking it. It is treated as a type name only in the specific local variable declaration context.