Java Tutorial
🔍

var (Local Type Inference)

var (Local Type Inference)

var, introduced in Java 10 (JEP 286), lets a local variable's type be inferred from its initializer instead of written out explicitly. It is local variable type inference, not dynamic typing — the compiler still determines a single, fixed type at the point of declaration, and every rule that applies to an explicitly typed variable still applies afterward.

What Is var?

var is a reserved type name that tells the compiler to work out a local variable's type from its initializer expression, rather than the developer writing that type by hand. Java 10's design goal for JEP 286 was narrow on purpose: reduce boilerplate for obvious, verbose declarations without turning Java into a dynamically typed language — the inferred type is still fixed forever at the point of declaration, and every later use is checked against it exactly as it would be for an explicitly typed variable.

Why var Was Introduced

Declaring a local variable with a long generic type meant writing that type twice — once on the left as the declared type, and again on the right as part of the constructor call or expression.

1// File: BeforeVar.java 2import java.util.*; 3 4public class BeforeVar { 5 public static void main(String[] args) { 6 Map<String, List<Integer>> scoresByPlayer = new HashMap<String, List<Integer>>(); 7 scoresByPlayer.put("Asha", List.of(10, 20, 30)); 8 9 for (Map.Entry<String, List<Integer>> entry : scoresByPlayer.entrySet()) { 10 System.out.println(entry.getKey() + ": " + entry.getValue()); 11 } 12 } 13}
Output:
Asha: [10, 20, 30]

With var, the compiler infers each variable's type from its initializer, so the type is written exactly once.

1// File: AfterVar.java 2import java.util.*; 3 4public class AfterVar { 5 public static void main(String[] args) { 6 var scoresByPlayer = new HashMap<String, List<Integer>>(); 7 scoresByPlayer.put("Asha", List.of(10, 20, 30)); 8 9 for (var entry : scoresByPlayer.entrySet()) { 10 System.out.println(entry.getKey() + ": " + entry.getValue()); 11 } 12 } 13}
Output:
Asha: [10, 20, 30]

scoresByPlayer is inferred as HashMap<String, List<Integer>> and entry as Map.Entry<String, List<Integer>> — nothing about the program's behavior changes, only how much of the type is written by hand.

One sentence before the diagram: the compiler still resolves a single, concrete type behind every var — only the source text changes, never the actual variable.

Source (what you write)                    Compiler resolves (what actually exists)

var scoresByPlayer =                  -->  HashMap<String, List<Integer>>
    new HashMap<String, List<Integer>>()       scoresByPlayer

for (var entry : map.entrySet())      -->  Map.Entry<String, List<Integer>>
                                                entry

Every method call, assignment, and type check made against scoresByPlayer or entry later in the method is checked against the resolved type on the right, not the word var on the left — the diagram's right-hand column is what the compiler, and every tool reading bytecode, actually sees.

Syntax Rules

var can only be used where the compiler has an initializer expression to infer a type from, and only for certain kinds of declarations.

ContextAllowed?
Local variable with an initializer — var x = 10;Yes
Local variable with no initializer — var x;No — compile error
Enhanced for-loop variable — for (var item : list)Yes
Try-with-resources resource — try (var r = ...)Yes
Instance or static fieldNo
Method return typeNo
Regular method parameterNo
Lambda parameter (Java 11+) — (var x, var y) -> ...Yes, but every parameter in the list must use var, not a mix
Initialized directly with nullvar x = null;No — compile error
Array-initializer shorthand — var arr = {1, 2, 3};No — compile error
1// File: VarTryWithResourcesExample.java 2 3public class VarTryWithResourcesExample { 4 5 static class Connection implements AutoCloseable { 6 private final String name; 7 Connection(String name) { 8 this.name = name; 9 System.out.println("Opening " + name); 10 } 11 void query() { 12 System.out.println("Querying " + name); 13 } 14 @Override 15 public void close() { 16 System.out.println("Closing " + name); 17 } 18 } 19 20 public static void main(String[] args) { 21 try (var connection = new Connection("OrdersDB")) { 22 connection.query(); 23 } 24 } 25}
Output:
Opening OrdersDB
Querying OrdersDB
Closing OrdersDB

var requires an initializer expression to infer a type from — no initializer, and no null literal, ever compiles, since neither one carries enough type information on its own.

Common Use Cases

Enhanced for-loops over collections with verbose generic types — as shown in the AfterVar example above, iterating a Map<String, List<Integer>> no longer requires spelling out Map.Entry<String, List<Integer>> for the loop variable.

Try-with-resourcesvar connection = new Connection("OrdersDB") in the example above avoids repeating the resource's type when it is already obvious from the constructor call on the right.

Capturing the result of a chained or stream-based expression — the type of a stream pipeline's result is often long enough that restating it on the left adds little value.

1// File: VarStreamResultExample.java 2import java.util.*; 3import java.util.stream.*; 4 5public class VarStreamResultExample { 6 public static void main(String[] args) { 7 var names = List.of("Neha", "Aarav", "Isha", "Kabir"); 8 9 var longNames = names.stream() 10 .filter(name -> name.length() > 4) 11 .collect(Collectors.toList()); 12 13 System.out.println(longNames); 14 } 15}
Output:
[Aarav, Kabir]

Declaring a variable whose type is already stated by a descriptive factory method or constructor namevar orderAggregator = new OrderAggregator(); tells a reader everything OrderAggregator orderAggregator = new OrderAggregator(); did, with less repetition.

Real-World Example

A grocery order aggregation utility groups a flat list of order items by category and prints a per-category total, using var throughout to avoid repeating the nested generic types involved.

1// File: OrderItem.java 2 3public record OrderItem(String productName, int quantity, double price) {}
1// File: OrderAggregator.java 2import java.util.*; 3 4public class OrderAggregator { 5 6 public Map<String, List<OrderItem>> groupByCategory( 7 List<OrderItem> items, Map<String, String> categoryByProduct) { 8 9 var grouped = new LinkedHashMap<String, List<OrderItem>>(); 10 11 for (var item : items) { 12 var category = categoryByProduct.getOrDefault(item.productName(), "Uncategorized"); 13 var categoryItems = grouped.computeIfAbsent(category, key -> new ArrayList<OrderItem>()); 14 categoryItems.add(item); 15 } 16 17 return grouped; 18 } 19}
1// File: OrderAggregatorDemo.java 2import java.util.*; 3 4public class OrderAggregatorDemo { 5 public static void main(String[] args) { 6 var items = List.of( 7 new OrderItem("Rice 5kg", 2, 340.0), 8 new OrderItem("Milk 1L", 3, 60.0), 9 new OrderItem("Toothpaste", 1, 95.0), 10 new OrderItem("Bread", 2, 45.0), 11 new OrderItem("Sugar 1kg", 1, 50.0) 12 ); 13 14 var categoryByProduct = Map.of( 15 "Rice 5kg", "Grocery", 16 "Milk 1L", "Dairy", 17 "Toothpaste", "Personal Care", 18 "Bread", "Bakery", 19 "Sugar 1kg", "Grocery" 20 ); 21 22 var aggregator = new OrderAggregator(); 23 var grouped = aggregator.groupByCategory(items, categoryByProduct); 24 25 for (var entry : grouped.entrySet()) { 26 var total = entry.getValue().stream() 27 .mapToDouble(item -> item.price() * item.quantity()) 28 .sum(); 29 System.out.println(entry.getKey() + ": " + entry.getValue().size() + " item(s), total = " + total); 30 } 31 } 32}
Output:
Grocery: 2 item(s), total = 730.0
Dairy: 1 item(s), total = 180.0
Personal Care: 1 item(s), total = 95.0
Bakery: 1 item(s), total = 90.0

A mistake that appears often in fresher pull requests is declaring grouped with the diamond operator alone — var grouped = new LinkedHashMap<>(); — instead of naming the generic types explicitly on the right, silently losing all type safety on both the key and the value. OrderAggregator above deliberately writes new LinkedHashMap<String, List<OrderItem>>() in full on the right-hand side specifically so var has something concrete to infer from — this exact gotcha is covered in depth in the Common Mistakes section below.

Combining var With Other Features

var in a lambda parameter list, added in Java 11, lets an annotation be attached to a lambda parameter that implicit typing alone could never support — covered in this series' Java 11 Features article. var combines naturally with records, exactly as OrderAggregator does above, since a record's type is often already obvious from the constructor call used to build it. var also pairs with pattern matching for switch and record patterns from Java 21, though in a pattern itself the more specific inferred type from the pattern's own deconstruction is usually preferable to var.

Best Practices

Use var when the right-hand side already makes the type obvious — a constructor call, a factory method with a descriptive name, or a loop variable whose collection type is visible nearby.

Avoid var when it would hide meaningful information — var result = process(data); tells a reader far less than OrderValidationResult result = process(data);, especially when process does not make its return type self-evident.

Always write the generic type explicitly on the right-hand side when using var with the diamond operator, exactly as OrderAggregator does with new LinkedHashMap<String, List<OrderItem>>(), rather than relying on the diamond operator's inference alone.

Choose a variable name that carries meaning on its own once the type is no longer spelled out on the left — var orderTotal = ... reads clearly, var x = ... does not.

Common Mistakes

Using var with the diamond operator on a generic constructor call is the single most common var mistake — with no explicit type on the left for the diamond to infer against, the compiler falls back to Object for every type parameter, silently defeating generics entirely.

1// File: VarDiamondMistake.java 2import java.util.*; 3 4public class VarDiamondMistake { 5 public static void main(String[] args) { 6 var list = new ArrayList<>(); 7 8 list.add("first"); 9 list.add(42); 10 11 System.out.println(list); 12 } 13}
Output:
[first, 42]

list is inferred as ArrayList<Object>, so adding a String and then an Integer to the same list both compile without complaint — exactly the kind of mixed-type list generics exist to prevent. Writing new ArrayList<String>() explicitly on the right, instead of relying on the diamond, avoids this entirely.

Assuming var can be used without an initializer, or initialized directly with null, overlooks that the compiler needs an initializer expression to infer a type from in the first place.

1// Neither of these compiles - there is nothing to infer a type from 2var count; 3var value = null;

Assuming var behaves like a dynamically typed variable, the way var does in JavaScript, is a conceptual mistake rather than a syntax one — once the compiler infers a type at declaration, that variable's type is fixed for its entire scope, and reassigning it to an incompatible type is still a compile error.

1// This does not compile - count's inferred type is int, fixed at declaration 2var count = 10; 3count = "now a string";

Interview Questions

Q1. What is var in Java, and which Java version introduced it?

var enables local variable type inference — the compiler determines a local variable's type from its initializer expression instead of the type being written explicitly. It was introduced in Java 10 via JEP 286. The nuance interviewers are listening for is whether you say "type inference," not "dynamic typing" — confusing the two is the single fastest way to lose credibility on this question.

Q2. Is var a keyword in Java?

No, var is a reserved type name, not a keyword. It can still be used as the name of a variable, method, or package — int var = 5; compiles — but it cannot be used as the name of a class or interface. Interviewers ask this specifically to see if you understand the difference between a keyword and a contextually reserved identifier, a distinction Java has used sparingly.

Q3. Can var be used for instance fields or method parameters?

No, for instance and static fields, or for regular method parameters. The one exception is a lambda expression's parameter list, where var has been allowed for every parameter since Java 11. The nuance here is knowing the lambda-parameter exception exists at all — many candidates confidently say "never," which is close but not fully correct.

Q4. What type does var infer when used with the diamond operator, e.g. var list = new ArrayList<>();?

It infers ArrayList<Object>, not a more specific type. With no explicit type on the left for the diamond operator to infer its type arguments from, the compiler falls back to Object, which silently defeats the type safety generics are meant to provide. Interviewers use this question specifically to check whether you have actually hit this bug yourself, since it rarely shows up in textbook explanations of var.

Q5. Can a variable declared with var be reassigned to a value of a different type later?

No. var only affects how the type is written at declaration — the compiler still infers and fixes one specific type at that point, and every subsequent assignment must still satisfy normal Java type-checking rules against that inferred type. This is the question that separates candidates who understand static type inference from those who assume var means "no type checking."

Q6. Why doesn't var x = null; compile?

Because var requires an initializer expression to infer a type from, and null by itself carries no type information the compiler can infer a specific type from. Writing String x = null; compiles, since the type is explicit rather than inferred. The nuance being tested is whether you can explain why, not just recite that it fails.

Q7. Can var be used in a lambda's parameter list?

Yes, since Java 11, but only if every parameter in that lambda's list uses var — mixing var with implicit or explicit types in the same parameter list is not allowed. The main reason to do this is to attach an annotation to a lambda parameter, which implicit typing alone does not support. Product-company interviewers in particular listen for whether you know why this feature exists, not just that it does.

FAQs

Does var make Java a dynamically typed language?

No. var only changes what is written at the declaration site — the compiler still infers and fixes a single static type at that point, and every later use of the variable is checked against that type exactly as it would be for an explicitly typed variable.

Can var be used for array declarations?

Yes, as long as an explicit array-creation expression appears on the right, such as var arr = new int[]{1, 2, 3};. The shorthand array-initializer syntax without new, like var arr = {1, 2, 3};, does not compile with var, since that shorthand only has a type in the context of an already-known target array type.

Does using var affect runtime performance?

No. Type inference happens entirely at compile time — the compiled bytecode for a var declaration is identical to the bytecode for the equivalent explicitly typed declaration, so there is no runtime difference at all.

Can var be used in an enhanced for-loop?

Yes, for the loop variable — for (var item : list) — which is one of the places var most reliably improves readability, especially when the collection's element type itself is a long generic type.

Is it good practice to use var everywhere?

No. var is most useful when the type is already obvious from the right-hand side; using it for an expression whose return type is not self-evident, such as the result of a method call with a vague name, can make code harder to read rather than easier.

Can var be used as a variable name?

Yes. Since var is a reserved type name rather than a keyword, int var = 5; and even var var = 5; are both legal, though using var as an identifier is confusing in practice and best avoided.

Does var work with try-with-resources?

Yes, try (var resource = ...) is valid as of Java 10, letting the resource's type be inferred exactly as it would be for any other local variable declaration.

Summary

var removes the need to write out a local variable's type twice when it is already obvious from its initializer, without changing Java's type system in any way — every variable declared with var still has one fixed, compiler-checked type from the moment it is declared. It works for local variables with an initializer, enhanced for-loop variables, try-with-resources resources, and lambda parameters, but never for fields, return types, or regular method parameters.

The habit worth carrying forward from this article's order-aggregation example is writing the generic type explicitly on the right whenever var is combined with the diamond operator, and reaching for var only where the right-hand side already tells the reader everything the left-hand type would have.

What to Read Next