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
varvar
// 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
varfortry-with-resources
// 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
// 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
// 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
varnew ArrayList<>()ArrayList<Object>new ArrayList<String>()Best Practices
Use var
// ✓ 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.
varvar