1. Introduction
Local variable type inference, introduced in Java 10 with the var
keyword, lets you declare local variables without explicitly stating their type. This feature aims to improve code readability and reduce verbosity, but it should be used carefully. This article teaches you how to use this concept effectively.
2. Motivation
Consider the following example:
Map<String, List<String>> departmentEmployees = new HashMap<>();
List<String> engineering = new ArrayList<>();
engineering.add("Alice");
engineering.add("Bob");
List<String> hr = new ArrayList<>();
hr.add("Eve");
departmentEmployees.put("Engineering", engineering);
departmentEmployees.put("HR", hr);
Now, look at the same example using var
:
var departmentEmployees = new HashMap<String, List<String>>();
var engineering = new ArrayList<String>();
engineering.add("Alice");
engineering.add("Bob");
var hr = new ArrayList<String>();
hr.add("Eve");
departmentEmployees.put("Engineering", engineering);
departmentEmployees.put("HR", hr);
This version is cleaner and easier to read while maintaining type safety.
In statically-typed languages like Java, type declarations can sometimes be repetitive and noisy, especially when the type is obvious from the context. Type inference with var
allows cleaner syntax while preserving type safety, helping developers focus on what the code does rather than what type each variable is.
3. How Local Variable Type Inference Works
Using var
tells the compiler to infer the variable’s type based on the initializer. Here’s a basic example:
var message = "Hello, world!"; // inferred as String
var list = new ArrayList<String>(); // inferred as ArrayList<String>
⚠️ Note:
var
is not a keyword, it’s a reserved type name.- It can only be used when the type can be inferred at compile-time.
- It is only allowed for local variables inside methods, constructors, initializers, and for-loops — not for fields, method parameters, or return types.
4. When to Use Local Variable Type Inference
There are many practical and safe places where using var
helps reduce noise without sacrificing clarity. The key is that the type should be obvious to the reader. Here are some good scenarios:
✅ The type is obvious
If the initializer is simple enough to allow you to easily deduce the type of variable.
var count = 10; // clearly an int
✅ Reducing generic boilerplate
When you use collections with generics, type declarations can be long and repetitive.
var map = new HashMap<String, List<Integer>>();
✅ In stream pipelines and loops
var
can simplify enhanced for-loops and improve readability in streams.
var names = List.of("Alice", "Bob", "Charlie");
for (var name : names) {
System.out.println(name);
}
✅ With try-with-resources
It is very handy in resource management blocks, where the type is clear from the right-hand side.
try (var reader = new BufferedReader(new FileReader("data.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
5. When Not to Use Local Variable Type Inference
Technically, you can use var
in many situations and the code will compile just fine. But for clean code and better readability, it’s best to avoid var
when the inferred type is not obvious or can lead to confusion.
❌ Type is unclear from the context
Avoid using var
when it’s not obvious what type is being returned.
var result = process(input); // what is result?
❌ Overuse in complex declarations
When dealing with unfamiliar libraries or nested types, it’s better to spell out the type explicitly.
var data = JsonParser.parse(input); // unclear what 'data' represents
❌ In lambda expressions with multiple parameters
While allowed since Java 11, using var
in lambdas often reduces readability and prevents mixing with inferred parameters.
Using the var
in a lambda expression has the same effect as not using it.
// These two are equivalent
BiFunction<Integer, Integer, Integer> sum = (var x, var y) -> x + y; // Not recommended - unreadable and error-prone
BiFunction<Integer, Integer, Integer> anotherSum = (x, y) -> x + y; // The compiler infers x and y types
System.out.println("Sum using var in lambda: " + sum.apply(3, 4)); // 7
System.out.println("Sum using var in lambda: " + anotherSum.apply(3, 4)); // 7
However, you cannot mix and match type inference and explicit types inside a lambda. The following are invalid and will not compile:
// ❌ Compilation errors
BiFunction<Integer, Integer, Integer> sum = (var x, y) -> x + y;
BiFunction<Integer, Integer, Integer> anotherSum = ( var x, Integer y) -> x + y;
6. Conclusion
Local variable type inference is a powerful tool when used wisely. Use it to reduce verbosity when the type is obvious and doesn’t harm readability. Avoid overusing it, especially when it hides important type information or complicates code clarity.
You can find the complete code of this article here in GitHub.