Comprehensive Guide to var in Java (Local Variable Type Inference)
Target versions: Java 10+, with notes up to Java 25
Feature: Local Variable Type Inference (LVTI) — JEP 286
1. What is var?
var in Java is local variable type inference:
- Introduced in Java 10 (JEP 286).
- It lets you omit the explicit type of a local variable.
- The compiler infers the type from the initializer expression.
- Java remains fully statically typed — only the syntax changes, not the type system.
Example:
var message = "Hello"; // inferred type: String
var count = 10; // inferred type: int
var list = new ArrayList<String>(); // inferred type: ArrayList<String>
This is not dynamic typing. Once the compiler chooses the type, it is fixed and checked at compile time.
2. Design Goals (High-Level)
The original design of var had several key goals:
- Reduce boilerplate — especially for long generic types.
- Improve readability when the type is obvious from the right-hand side.
- Keep Java strongly & statically typed — no runtime type changes.
- Keep inference local and simple — based only on the initializer expression.
- Avoid “type guessing” — inference should be predictable and obvious.
Because of these goals, the language intentionally restricts where var can be used (only local variables, not fields / parameters / returns).
3. Where var Can Be Used
var is allowed only in local variable declarations that:
- Are inside a method, constructor, or initializer block.
- Have an initializer (i.e., use
=to assign a value when declaring).
Concretely, you can use var in:
3.1. Local Variables in a Block
void process() {
var url = new URL("https://example.com");
var conn = url.openConnection();
var in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
}
Equivalently:
URL url = new URL("https://example.com");
URLConnection conn = url.openConnection();
BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
3.2. Traditional for Loops
for (var i = 0; i < 10; i++) {
System.out.println(i);
}
Here:
- i is inferred as int.
3.3. Enhanced for-each Loops
var map = Map.of("a", 1, "b", 2);
for (var entry : map.entrySet()) {
// entry is Map.Entry<String, Integer>
System.out.println(entry.getKey() + " = " + entry.getValue());
}
This is very common with maps, lists, sets, etc.
3.4. try-with-resources
Path path = Path.of("data.txt");
try (var reader = Files.newBufferedReader(path)) {
System.out.println(reader.readLine());
}
reader is inferred as BufferedReader.
4. Where var Cannot Be Used (Hard Language Rules)
These are compile-time errors — they are disallowed by the Java language specification.
4.1. Not for Fields (Instance or Static)
class MyClass {
var field = 10; // ❌ illegal: cannot use 'var' for fields
static var staticField = 20; // ❌ illegal
}
var is strictly local — fields must always have an explicit type.
4.2. Not for Method or Constructor Parameters
// ❌ illegal
void doSomething(var x) {
System.out.println(x);
}
// ❌ illegal
MyClass(var value) {
// ...
}
Parameters must declare an explicit type.
4.3. Not for Return Types
// ❌ illegal
var compute() {
return 42;
}
// ✅ legal (usual Java)
int compute() {
return 42;
}
var is not allowed in the method signature.
4.4. Not for Uninitialized Local Variables
// ❌ illegal
var x;
x = 10;
var requires an initializer, because the type is derived from the right-hand side.
4.5. Not with null Literal Alone
// ❌ illegal: type cannot be inferred from 'null'
var x = null;
However, you can use a cast:
// ✅ legal
var name = (String) null; // type is String, value is null
4.6. Not with Multiple Declarators in One Statement
// ❌ illegal
var a = 1, b = 2;
// ✅ legal with explicit type
int a = 1, b = 2;
With var, one declaration per variable is required.
4.7. Not with Array Declaration Using [] After the Name
// ❌ illegal
var arr[] = {1, 2, 3};
// ✅ legal
int[] arr = {1, 2, 3};
// ✅ legal (with explicit type on RHS)
var arr2 = new int[]{1, 2, 3};
Use var only before the variable name, not with a trailing [].
4.8. Not with “Short” Array Initializer Without Type
// ❌ illegal
var arr = {1, 2, 3};
// ✅ legal
int[] arr = {1, 2, 3};
// ✅ legal
var arr2 = new int[]{1, 2, 3};
4.9. Not with Lambdas Without Target Type
A lambda expression by itself does not have a type; it has a “poly expression” shape and needs a target functional interface.
// ❌ illegal
var runnable = () -> System.out.println("Hi");
// ✅ legal (cast gives a target type)
var runnable2 = (Runnable) () -> System.out.println("Hi");
The same applies to method references:
// ❌ illegal
var ref = String::trim;
// ✅ legal
var ref2 = (UnaryOperator<String>) String::trim;
4.10. Not for Catch Parameters
try {
// ...
} catch (var e) { // ❌ illegal
e.printStackTrace();
}
Catch parameters must declare an explicit type.
5. Type Inference Rules (How the Compiler Decides the Type)
5.1. Inference is Based on the Initializer Expression Only
The compiler infers the type from the right-hand side of the assignment. Subsequent assignments do not change it.
var value = 10; // type: int
value = 20; // OK
value = "hi"; // ❌ compile-time error: String not compatible with int
5.2. Simple Literals
var i = 1; // int
var l = 1L; // long
var d = 1.0; // double
var f = 1.0f; // float
var s = "text"; // String
var b = true; // boolean
var ch = 'a'; // char
The usual rules for numeric literals apply.
5.3. Generic Types
var list = new ArrayList<String>(); // ArrayList<String>
var set = Set.of("a", "b"); // immutable Set<String>
var map = new HashMap<String, Integer>(); // HashMap<String, Integer>
The compiler infers the full generic type, including type parameters.
5.4. Diamond Operator (<>) and Pitfalls
If the diamond operator has no contextual type, it may infer Object:
var list = new ArrayList<>(); // type: ArrayList<Object>
list.add(1);
list.add("two"); // compiles (because list is ArrayList<Object>)
This can cause subtle problems because you lose generic type safety.
Better:
var list = new ArrayList<String>(); // type: ArrayList<String>
list.add("ok");
// list.add(123); // ❌ compile error
or:
List<String> list = new ArrayList<>();
5.5. Conditional Expressions (Ternary Operator)
The type of a conditional (condition ? expr1 : expr2) is the least upper bound (LUB) of both branches.
var x = flag ? 1 : 2; // both int → type int
var y = flag ? 1 : 2L; // int and long → type long
var z = flag ? "a" : null; // String and null → type String
The usual Java rules for conditional expression typing apply; var just captures the resulting type.
5.6. Method Calls and Generic Methods
If the right-hand side is a method call, the variable type is the method’s return type after generic inference.
static <T> List<T> toList(T... values) {
return Arrays.asList(values);
}
var ints = toList(1, 2, 3); // type: List<Integer>
var strings = toList("a", "b"); // type: List<String>
var does not change how generics work; it simply uses the final inferred type.
5.7. Anonymous Classes
var obj = new Object() {
String hello() { return "Hi"; }
};
The inferred type is the anonymous class type, not just Object.
Within the same scope, you can call methods defined in that anonymous class:
var obj = new Object() {
String hello() { return "Hi"; }
};
System.out.println(obj.hello()); // OK
(You still cannot return this variable as a public type other than Object or an interface it implements.)
6. var + Lambdas (Local Variables) vs var In Lambda Parameters
There are two distinct features:
varfor local variables (Java 10).varin lambda parameters (Java 11, JEP 323).
6.1. var as Local Variable Holding a Lambda
Runnable r = () -> System.out.println("Hi"); // OK
var r2 = (Runnable) () -> System.out.println("Hi"); // OK
// var r3 = () -> System.out.println("Hi"); // ❌ illegal, no target type
You must give the lambda a target type (e.g., via an explicit cast or assignment to a typed variable) before var can be used.
6.2. var in Lambda Parameter Lists (Java 11+)
This is a separate feature but often grouped mentally with var.
// Before:
(list) -> list.size()
// Java 11+:
(var list) -> list.size()
It’s mainly useful if you want to annotate lambda parameters or mark them final:
(@Nonnull var s) -> s.trim()
(final var x, final var y) -> x + y
Rule: you cannot mix var and explicit types in the same parameter list:
(var x, y) -> x + y; // ❌ illegal
(var x, int y) -> x + y; // ❌ illegal
(var x, var y) -> x + y; // ✅ legal
7. Semantics: Scope, Definite Assignment, and Finality
var follows all the usual Java rules for local variables:
- Scope: from the point of declaration to the end of the block (or loop, etc.).
- Definite assignment: must be assigned before use (and
varrequires assignment at declaration). - final / effectively final rules apply exactly as for explicitly typed locals.
final var count = 10; // final local variable
// count = 20; // ❌ not allowed
If you don’t write final but never reassign the variable, it is effectively final, which still allows use in inner classes and lambdas:
var base = 10; // effectively final if never reassigned
Runnable r = () -> System.out.println(base); // OK
8. Modifiers and Annotations with var
You can combine var with modifiers and annotations, just like with explicit types.
8.1. With final
final var count = 10;
Equivalent to:
final int count = 10;
8.2. With Annotations
@Nonnull
var name = "Ali";
final @Nonnull var user = "Bob";
The annotation belongs to the inferred type in the same way it would if you wrote the type explicitly.
9. var and Keywords / Identifiers
var is a “reserved type name” or contextual keyword, not a classic keyword like class or if.
- You cannot use
varas a class or interface name. - You can use
varas a variable name where the compiler expects an identifier instead of a type:
int var = 5; // ✅ legal (variable name is literally "var")
void method() {
int var = 10; // also legal
}
However, you cannot then use var as a type on a local declaration:
var var = 5; // here the first 'var' is the special type, the second 'var' is the name
This is legal but confusing — avoid it!
10. Bytecode and Performance
varis purely a compiler feature.- The generated bytecode is identical to what you’d get by using the explicit type.
- There is no runtime overhead and no change in performance.
Example:
var list = new ArrayList<String>();
and
ArrayList<String> list = new ArrayList<String>();
produce essentially the same bytecode.
11. Common Pitfalls and Traps
11.1. Overly Generic Types (Especially with new ArrayList<>())
var list = new ArrayList<>(); // ArrayList<Object>
list.add(1);
list.add("two"); // compiles (mixed types allowed)
This defeats the purpose of generics and may blow up later.
Better:
var list = new ArrayList<String>(); // ArrayList<String>
11.2. Reduced Readability / Obscure Types
Overuse of var can make code harder to understand:
var data = service.process(); // what type is data?
var config = loader.load(); // what is config?
Compare with:
CustomerResponse data = service.process();
Configuration config = loader.load();
Guideline: use var only when the inferred type is clear from the context.
11.3. Numeric Types and Conversions
var x = 1; // int
var y = 1L; // long
var z = 1.0; // double
var f = 1.0f; // float
When doing arithmetic and bitwise operations, it may be more readable and safer to spell out the type explicitly, especially when mixing types.
11.4. Misconception: “Dynamic Typing”
var obj = "text"; // String
obj = 123; // ❌ compile-time error
Some developers coming from dynamic languages may think var behaves like JavaScript’s let or var. It does not. The type does not change at runtime.
11.5. Confusing Inference for Conditional and Generic Expressions
If the type you expect is not exactly what Java’s inference chooses (for example, List<? extends Number> vs List<Number>), using var can hide that discrepancy. In such situations, an explicit type can make intent clearer.
12. Style Guidelines / Best Practices
These are practical rules that many teams adopt.
12.1. Good Places to Use var ✅
- When the type is very obvious from the right-hand side:
java
var url = new URL("https://example.com");
var out = new FileOutputStream("log.txt");
- When the type name is long and repetitive, and the RHS already shows it:
java
var map = new HashMap<String, List<Customer>>();
- In loops where the type is clear from the collection:
java
for (var customer : customers) {
sendEmail(customer);
}
- For intermediate variables in complex expressions, especially with streams:
java
var stream = Files.lines(path);
var filtered = stream.filter(line -> !line.isBlank());
12.2. Places to Avoid var ❌
- When the RHS gives no clue about the type:
java
var result = compute(); // compute returns what exactly?
- When documenting or teaching APIs where explicit types help understanding.
- When working with tricky numeric code (bit manipulation, casts, etc.).
- When using
varwould hide important information about mutability or domain concepts.
12.3. Team Conventions (Common Patterns)
Teams often adopt rules like:
- “Use
varfor local variables when the type is obvious from the initializer.” - “Don’t use
varfor public API examples or code meant to teach newcomers.” - “Prefer explicit types for domain objects (like
Customer,Order) in business logic.”
13. Typical Interview Questions and Answers
You can use this section to quickly revise for interviews.
Q1. Does var make Java dynamically typed?
A: No. var is purely a compile-time feature. The variable still has a fixed static type inferred from the initializer.
Q2. Where can var be used?
A: Only for local variables with initializers, including:
- Local variables in methods / constructors
- Loop variables (
forand enhancedfor) - Resource variables in
try-with-resources
Q3. Where cannot var be used?
A:
- Fields (instance or static)
- Method or constructor parameters
- Return types
- Catch parameters
- Uninitialized local variables
- Variables initialized with plain
null - Multi-variable declarations in one statement
- Certain array forms (
var arr[] = ...) - Lambdas / method references without a target type
Q4. What version introduced var?
A: Java 10, via JEP 286 (Local Variable Type Inference).
Q5. Does var affect performance or bytecode?
A: No. It only affects the source code; the compiled bytecode is the same as if you had written the explicit type.
Q6. How is var different from using Object?
A:
Object x = something;→ static type isObject, you often need casts to use it.var x = something;→ static type is the precise inferred type (e.g.String,List<Integer>, etc.), so you usually don’t need casts.
14. Summary
varis a local variable type inference feature that reduces verbosity without changing Java’s static type system.- It is restricted by design to keep the language clear and safe.
- Used wisely,
varcan significantly improve code readability and reduce noise, especially with generics and builder-style APIs. - Overuse or careless use can reduce readability and hide types, so you should follow sensible style guidelines.
This guide covers the behavior, usage, limitations, and best practices for var in Java from Java 10 onward. You can use it as a reference for both interview preparation and real-world coding.