Contents
- Basic Usage
- Where var Is Allowed
- Where var Is NOT Allowed
- var with Generics and Collections
- Best Practices
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:
// 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);
}
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:
// 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
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:
// 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 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:
// 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>().
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:
// ✓ 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.