Contents

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.