var Keyword
The var keyword, introduced in Java 10 as local variable type inference, lets a local variable declaration omit its explicit type and have the compiler infer it from the initializer expression on the right-hand side, while the variable remains just as statically typed at compile time as if the type had been written out explicitly — var is purely a source-code convenience, not a shift toward dynamic typing. This is a narrower, more conservative feature than type inference in languages like C# (var) or Kotlin (val/var): it applies only to local variables with an initializer, never to fields, method parameters, or return types, and the compiler still performs full static type checking using the inferred type. This entry covers the motivation and explicit scope restrictions, how type inference actually resolves (including with generics, diamond operator interaction, and anonymous classes), the readability tradeoffs and style guidance the OpenJDK team itself published, and the specific cases where var cannot be used.
Motivation and Scope — What var Is and Deliberately Is Not
// ── Before Java 10 — type written out in full, often redundantly ───────
Map<String, List<Customer>> customerOrders = new HashMap<String, List<Customer>>();
ArrayList<String> names = new ArrayList<String>(); // pre-diamond-operator era
StringBuilder sb = new StringBuilder();
// ── Java 10+ — var infers the type from the initializer ─────────────────
var customerOrders = new HashMap<String, List<Customer>>(); // inferred: HashMap<String, List<Customer>>
var names = new ArrayList<String>(); // inferred: ArrayList<String>
var sb = new StringBuilder(); // inferred: StringBuilder
// ── var is static typing, NOT dynamic typing — type is fixed at compile time
var count = 10; // inferred type: int, fixed for this variable's entire scope
// count = "hello"; // COMPILE ERROR — cannot assign a String to an int-typed variable,
// exactly the same error as if "int count = 10;" had been written
// ── Where var IS allowed — local variables WITH an initializer ─────────
for (var i = 0; i < 10; i++) { /* i inferred as int */ }
try (var reader = new BufferedReader(new FileReader("f.txt"))) { /* inferred as BufferedReader */ }
// ── Where var is NOT allowed — fields, parameters, return types, no initializer
class Example {
// private var name; // COMPILE ERROR — fields cannot use var
// public var process(var input) { // COMPILE ERROR — params and return types cannot use var
// var x; // COMPILE ERROR — no initializer to infer from
// }
}Type Inference Resolution — Generics, Diamond Operator, and Anonymous Classes
// ── Generic type inference — fully preserved, not erased to raw types ──
var orders = new HashMap<String, List<Customer>>();
// orders is fully typed as HashMap<String, List<Customer>> — generics work exactly as normal:
// orders.put("x", List.of()); // type-checked normally
// orders.put("x", "not a list"); // COMPILE ERROR — type mismatch, exactly as with explicit type
// ── PITFALL: var + empty diamond operator infers Object, not the intended type
var pitfallList = new ArrayList<>(); // inferred as ArrayList<Object> — NOT what was likely intended!
pitfallList.add("a string");
pitfallList.add(42); // compiles fine — element type is Object, no real type safety
// CORRECT — specify the type argument explicitly when combining var with diamond:
var properList = new ArrayList<String>(); // inferred as ArrayList<String> — full type safety restored
// properList.add(42); // now correctly a COMPILE ERROR
// ── var with anonymous classes — exposes extra methods the named type doesn't have
var runner = new Runnable() {
@Override public void run() { System.out.println("Running"); }
public void extraDiagnosticMethod() { System.out.println("Extra info!"); } // NOT on Runnable
};
runner.run(); // fine — part of Runnable
runner.extraDiagnosticMethod(); // ALSO fine — var preserves the anonymous class's full type,
// including methods beyond what Runnable itself declares
// If this had been written as "Runnable runner = new Runnable() {...}" instead,
// extraDiagnosticMethod() would NOT be callable on "runner" at all — Runnable's
// declared type doesn't include it, even though the actual object has it
// ── Array syntax — initializer-side brackets only, not "C-style" ───────
var arr = new int[]{1, 2, 3}; // OK — inferred as int[]
// var arr2[] = new int[]{1, 2, 3}; // COMPILE ERROR — C-style brackets after var not allowedReadability Tradeoffs and Style Guidance
// ── Good var usage — type is fully evident from the initializer ───────
var customers = new ArrayList<Customer>(); // clear: List of Customer
var connection = DriverManager.getConnection(url); // clear from context/name: a Connection
for (var entry : map.entrySet()) { /* clear: Map.Entry<K,V> */ }
// ── Discouraged var usage — initializer doesn't make the type obvious ──
var result = service.process(input); // What IS "result"? Reader has to go look up process()'s
// return type — var here REMOVES useful information
// rather than removing useful redundancy
// Better — explicit type aids readability here, since chained/delegated calls
// don't make the resulting type visually obvious:
OrderConfirmation result2 = service.process(input);
// ── Discouraged — explicit type served as helpful in-line documentation ─
// Before: explicit types help distinguish similar-looking nearby variables
Iterator<String> nameIterator = names.iterator();
Iterator<Integer> idIterator = ids.iterator();
// After: var makes the two declarations look identical, losing that distinction
// at the declaration site (though the names still help somewhat):
var nameIterator2 = names.iterator();
var idIterator2 = ids.iterator();
// Style guidance: prefer the explicit form here, since the types being iterated
// are meaningfully different and not obvious purely from variable naming
// ── var has ZERO runtime/bytecode effect — purely a source-level convenience
var x = 42; // compiles to bytecode IDENTICAL to:
int x2 = 42; // ...this. No performance difference, no behavioral difference whatsoever.