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