Java 10: var
Java 10 introduced var — a way to let the compiler infer the type of a local variable so you do not have to write it yourself. Your code gets shorter and easier to scan, and you lose nothing: Java remains a statically typed language, just with less ceremony.
What Is var?
When you declare a local variable, you usually write the type on the left and the initializer on the right. With var, you write var instead of the type and let the compiler figure it out from the right-hand side.
// Without var — type is written explicitly
String message = "Hello, Java 10!";
List<String> names = new ArrayList<>();
// With var — compiler infers the type
var message = "Hello, Java 10!";
var names = new ArrayList<String>();
At the bytecode level, message is still a String and names is still an ArrayList<String>. var is a compile-time convenience, not a runtime type change. After compilation, there is literally no trace of var in the .class file.
Note:
varis NOT a keyword — it is a context-sensitive reserved type name. That means you can still name a variable, method, or packagevar(though you really should not). Existing code that usesvaras an identifier continues to compile with Java 10+.
Where You Can Use var
var works only for local variables with an initializer. It is intentionally limited to these situations:
| Context | Allowed? |
|---|---|
| Local variable with initializer | Yes |
| Enhanced for-loop variable | Yes |
| try-with-resources variable | Yes |
| Method parameter | No |
| Constructor parameter | No |
| Method return type | No |
| Field declaration | No |
| Array initializer (shorthand) | No |
import java.util.List;
public class VarExamples {
public static void main(String[] args) {
// 1. Simple local variable
var greeting = "Hello, World!";
System.out.println(greeting);
// 2. Enhanced for-loop
var fruits = List.of("apple", "banana", "cherry");
for (var fruit : fruits) {
System.out.println(fruit.toUpperCase()); // fruit is inferred as String
}
// 3. try-with-resources
try (var reader = new java.io.StringReader("test")) {
System.out.println((char) reader.read());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Output:
Hello, World!
APPLE
BANANA
CHERRY
t
Where You Cannot Use var
The compiler must be able to infer the type from the initializer. If it cannot, the code will not compile.
// COMPILE ERROR — no initializer, type cannot be inferred
var x;
// COMPILE ERROR — null has no type
var y = null;
// COMPILE ERROR — array shorthand is not allowed
var z = {1, 2, 3};
// OK — explicit array creation works
var arr = new int[]{1, 2, 3};
var with Generics: Be Explicit on the Right
When using var with generic collections, make sure the right-hand side carries enough type information. Otherwise you may get an unintended raw type or a wider type than you want.
import java.util.ArrayList;
import java.util.List;
public class VarGenerics {
public static void main(String[] args) {
// Diamond operator alone is enough — type is inferred from left side normally,
// but with var there is no left side to anchor it.
// This gives you ArrayList<Object>, not ArrayList<String>!
var list1 = new ArrayList<>(); // inferred: ArrayList<Object>
// Be explicit about the type argument
var list2 = new ArrayList<String>(); // inferred: ArrayList<String>
list2.add("hello");
System.out.println(list2.get(0).toUpperCase());
// With List.of, the type is inferred from the elements
var list3 = List.of("a", "b", "c"); // inferred: List<String>
System.out.println(list3);
}
}
Output:
HELLO
[a, b, c]
Warning: Using the diamond operator
<>withvarinfersObjectas the type argument. Always provide the type argument on the right-hand side when usingvarwith generic collections.
var in Loops
One of the most natural uses of var is in loop variables — especially when the iterator type name is long.
import java.util.Map;
public class VarLoop {
public static void main(String[] args) {
var scores = Map.of("Alice", 95, "Bob", 87, "Carol", 91);
// Without var, this is verbose:
// for (Map.Entry<String, Integer> entry : scores.entrySet())
for (var entry : scores.entrySet()) {
System.out.println(entry.getKey() + " scored " + entry.getValue());
}
}
}
Output:
Alice scored 95
Bob scored 87
Carol scored 91
The entry variable is correctly inferred as Map.Entry<String, Integer>, so you still get full IDE auto-complete and compile-time type checking.
var with Lambda Parameters (Java 11)
Java 10 introduced var for local variables. Java 11 extended it to lambda parameters, which lets you add annotations to lambda arguments (previously not possible).
import java.util.List;
import java.util.function.Function;
public class VarLambda {
public static void main(String[] args) {
// Java 11+: var in lambda parameters
Function<String, String> upper = (var s) -> s.toUpperCase();
System.out.println(upper.apply("modern java"));
// The main use case: adding annotations to parameters
// (var s) -> s.toUpperCase() allows (@NotNull var s) -> s.toUpperCase()
}
}
Output:
MODERN JAVA
Note: In a lambda, you must use
varfor ALL parameters or NONE. You cannot mixvarwith explicit types or implicit types in the same lambda:(var x, String y) -> ...is a compile error.
When to Use var — and When Not To
var is a tool for reducing noise, not for hiding information. Use your judgment:
Good uses of var:
// Long generic types — var removes visual clutter
var usersByCity = new HashMap<String, List<User>>();
// The type is obvious from the right-hand side
var count = 0;
var name = "Alice";
var reader = new BufferedReader(new FileReader("data.txt"));
Avoid var when the type is not obvious:
// BAD — what does getResult() return? Reader has to check the method signature.
var result = getResult();
// BETTER — be explicit when the type adds meaning
ProcessingResult result = getResult();
Tip: A good rule of thumb: if you would have to look up the method signature to know what type
varinfers, write the type explicitly. If the right-hand side makes it obvious (a constructor call, a literal, a factory method),varis a good fit.
Here is a quick decision guide:
| Situation | Use var? |
|---|---|
Constructor call: new ArrayList<String>() | Yes |
Obvious literal: 0, "hello", true | Yes |
| Long, verbose generic type | Yes |
| Opaque method return value | Avoid |
| Return type or field | Not allowed |
| Code shared with beginners who find it confusing | Avoid |
Under the Hood
var is purely a compile-time feature. Here is what actually happens when you write var x = someExpression:
- The Java compiler evaluates the type of
someExpressionusing standard type inference rules. - It replaces
varwith the inferred concrete type in the internal AST (Abstract Syntax Tree). - The compiled bytecode contains exactly the same
LOAD/STOREinstructions as if you had written the explicit type yourself. - The
LocalVariableTableattribute in the.classfile records the inferred type, so your IDE and debugger see the real type correctly.
You can confirm this with the javap -v command. Run it on a class that uses var — you will see the fully resolved type in the local variable table, not var anywhere.
// Source
var count = 42;
// What javap sees in the bytecode (equivalent explicit type)
int count = 42;
The JVM has no concept of var at all. It only ever sees the inferred concrete type.
Note: See javap Tool for a walkthrough of reading compiled bytecode, and JIT Compilation & Bytecode for how the JVM executes those instructions.
Interaction with Interfaces
var infers the actual (most specific) type, not a declared interface. This can give you access to methods that the interface does not expose — but it also makes the code more brittle if the implementation changes.
import java.util.ArrayList;
import java.util.List;
public class VarInterface {
public static void main(String[] args) {
// var infers ArrayList<String>, not List<String>
var list = new ArrayList<String>();
list.add("hello");
list.trimToSize(); // ArrayList-specific method — works!
// If you had written: List<String> list = new ArrayList<>()
// then list.trimToSize() would not compile (List has no trimToSize)
}
}
This is a double-edged sword. If you later change the right-hand side to new LinkedList<String>(), the trimToSize() call breaks. Explicitly typing as List<String> would have caught this earlier. Use var thoughtfully here.
Compatibility and Tooling
varrequires Java 10 or later (JEP 286).- Lambda
varparameters require Java 11 or later (JEP 323). - All modern IDEs (IntelliJ IDEA, Eclipse, VS Code with Java extensions) fully support
varwith type hints on hover — you always know what the inferred type is. - Static analysis tools like Checkstyle let you configure rules about when
varis allowed (e.g., restrict to local variables only, enforce explicit types for non-obvious initializers).
Related Topics
- Variables — The fundamentals of declaring and using variables in Java before
varcame along. - Data Types — Understanding Java’s type system helps you reason about what
varinfers. - Java 8 Features — Lambda expressions, streams, and functional interfaces — the foundation
varbuilds on. - Lambda Expressions — Java 11 extended
varto lambda parameters, making annotations on parameters possible. - Generics — Understanding generics is essential for using
varsafely with collections. - Modern Java (9–21) — Overview of all major Java improvements since Java 8, including records, sealed classes, and virtual threads.