Java var Keyword Explained — Local Type Inference in Java 10
Java has always been famous for being explicit. You declare an int, you write int. You want a list of strings, you write List
Java 10 introduced the var keyword to solve exactly that problem. It's a feature called local variable type inference, and the idea is simple: when the type of a variable is already obvious from the right-hand side of an assignment, there's no reason you should have to type it out twice. Java reads what you're assigning, figures out the type itself, and locks it in at compile time — exactly as if you had typed it manually. var doesn't make Java dynamically typed. The type is still fixed forever at the moment you declare the variable. var just lets Java do the obvious deduction so you don't have to.
By the end of this article you'll understand exactly what var does under the hood, where you can and can't use it, how it compares to explicitly typed declarations, and the real mistakes that trip up beginners. You'll also be ready to answer the var questions that show up in Java interviews far more often than most people expect.
What var Actually Does — and What It Doesn't Do
The first thing to get straight is what var is NOT. It is not JavaScript's var. It is not a dynamic type. It does not mean 'this variable can hold anything'. Java is still 100% statically typed, and var plays by all the same rules.
Here's what actually happens when Java sees var: it looks at the value on the right-hand side of your assignment, determines the compile-time type of that value, and treats your variable declaration as if you had written that type explicitly. This all happens at compile time — by the time your program runs, var is completely gone. The JVM never even sees the word var.
Think of it as a shorthand that your editor and compiler expand for you. You write var price = 9.99 and the compiler reads it as double price = 9.99. You write var greeting = "Hello" and the compiler reads it as String greeting = "Hello". The variable type is inferred once, locked in forever, and from that line onward you can only assign compatible values to it — exactly the same as if you had written the type yourself.
This is fundamentally different from languages like Python or JavaScript where a variable can change its type at runtime. In Java, var just moves the type-writing responsibility from you to the compiler, for cases where the type is already obvious from context.
public class VarBasicDemo { public static void main(String[] args) { // Without var — the traditional way. // Notice we write 'String' twice: once on the left, once implicitly through the literal. String productName = "Wireless Keyboard"; // With var — the compiler sees "Wireless Keyboard", knows it's a String, // and treats this exactly as: String discountedName = "Mechanical Mouse"; var discountedName = "Mechanical Mouse"; // var works with any type the compiler can infer from the right-hand side. var itemCount = 42; // inferred as int var itemPrice = 29.99; // inferred as double var isInStock = true; // inferred as boolean var firstLetter = 'K'; // inferred as char // Once inferred, the type is LOCKED. The line below would cause a compile error: // itemCount = "forty-two"; // ERROR: incompatible types: String cannot be converted to int System.out.println("Product : " + productName); System.out.println("Discount Item : " + discountedName); System.out.println("Count : " + itemCount); System.out.println("Price : $" + itemPrice); System.out.println("In Stock: " + isInStock); System.out.println("Initial : " + firstLetter); // Let's prove the type is real — getClass() returns the actual runtime type. System.out.println("\nRuntime type of discountedName : " + ((Object) discountedName).getClass().getSimpleName()); System.out.println("Runtime type of itemPrice : " + ((Object) itemPrice).getClass().getSimpleName()); } }
Discount Item : Mechanical Mouse
Count : 42
Price : $29.99
In Stock: true
Initial : K
Runtime type of discountedName : String
Runtime type of itemPrice : Double
Where You Can Use var — and Where Java Flat-Out Refuses
var has one strict rule: it only works for local variables — variables declared inside a method, constructor, or initializer block — and only when the compiler can infer the type from the initializer on the same line.
That means three things must be true: (1) you're inside a method body or similar local scope, (2) you're initializing the variable right then and there — not just declaring it, and (3) the right-hand side is not null or a lambda on its own, because those don't have a concrete type the compiler can pin down.
You cannot use var for class-level fields (instance or static variables), method parameters, method return types, or constructor parameters. The Java designers made this choice deliberately — those positions are part of a class's public contract, which should always be explicit so other developers reading the signature understand it immediately without tracing through code.
The most powerful and practical use case is with generic types. Writing Map
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class VarScopeRules { // RULE 1: var is NOT allowed for class fields. // The line below would NOT compile — uncomment to see the error. // var storeId = 101; // ERROR: 'var' is not allowed here public static void main(String[] args) { // RULE 2: var MUST have an initializer on the same line. // var pendingOrders; // ERROR: cannot use 'var' on variable without initializer // RULE 3: var cannot be initialized to null alone — // the compiler can't figure out what type you want. // var customerName = null; // ERROR: cannot infer type for local variable customerName // ✅ VALID — simple local variables var storeName = "TheCodeForge Shop"; // inferred: String var totalItems = 150; // inferred: int var taxRate = 0.08; // inferred: double // ✅ VALID — var shines with verbose generic types. // Old way (perfectly fine but noisy): Map<String, List<Integer>> categoryToProductIds = new HashMap<>(); // New way with var — same type, same safety, half the characters: var categoryMap = new HashMap<String, List<Integer>>(); // ^ inferred as: HashMap<String, List<Integer>> // ✅ VALID — var inside a for loop (very common real-world use) var productNames = new ArrayList<String>(); productNames.add("Laptop Stand"); productNames.add("USB Hub"); productNames.add("Ergonomic Chair"); // var in an enhanced for loop — 'productName' is inferred as String for (var productName : productNames) { System.out.println(" Product: " + productName.toUpperCase()); } // ✅ VALID — var in a traditional for loop for (var index = 0; index < productNames.size(); index++) { System.out.println(" Index " + index + " -> " + productNames.get(index)); } System.out.println("\nStore : " + storeName); System.out.println("Items : " + totalItems); System.out.println("Tax : " + (taxRate * 100) + "%"); System.out.println("Category map type: " + categoryMap.getClass().getSimpleName()); } }
Product: USB HUB
Product: ERGONOMIC CHAIR
Index 0 -> Laptop Stand
Index 1 -> USB Hub
Index 2 -> Ergonomic Chair
Store : TheCodeForge Shop
Items : 150
Tax : 8.0%
Category map type: HashMap
var With Real Objects and Method Return Values
One of the places var earns its keep most visibly is when you're working with the return values of methods. If a method returns a long generic type like Optional
The same applies when you instantiate objects. If you write var order = new CustomerOrder() the type is obviously CustomerOrder — there's no ambiguity and no benefit to writing it twice.
However, there's a subtlety worth understanding here around polymorphism. When you write CustomerOrder order = new PriorityOrder() you're explicitly telling Java to treat this object as the CustomerOrder type, even though the actual object is a PriorityOrder. If you write var order = new PriorityOrder() the inferred type is PriorityOrder — the more specific type. That's usually what you want in local code, but it's worth knowing the difference.
Below is a realistic example that mirrors what you'd actually write in a small application, so you can see how var fits into real code rather than toy examples.
import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class VarWithObjects { // A simple class representing an order in an e-commerce system. static class Order { private final int orderId; private final String customerName; private final double totalAmount; private final LocalDate orderDate; Order(int orderId, String customerName, double totalAmount) { this.orderId = orderId; this.customerName = customerName; this.totalAmount = totalAmount; this.orderDate = LocalDate.now(); // today's date } // Returns an Optional — caller must handle the case where name is missing. Optional<String> getCustomerName() { return Optional.ofNullable(customerName); } double getTotalAmount() { return totalAmount; } int getOrderId() { return orderId; } LocalDate getOrderDate(){ return orderDate; } @Override public String toString() { return "Order{id=" + orderId + ", customer='" + customerName + "', total=$" + totalAmount + "}"; } } // A helper method that returns a list of orders. static List<Order> fetchRecentOrders() { var orders = new ArrayList<Order>(); // var: inferred as ArrayList<Order> orders.add(new Order(1001, "Alice Martin", 149.99)); orders.add(new Order(1002, "Bob Chen", 89.50)); orders.add(new Order(1003, "Sara Lopez", 220.00)); return orders; } public static void main(String[] args) { // var captures the return type of fetchRecentOrders() — which is List<Order>. // No need to write 'List<Order>' twice. var recentOrders = fetchRecentOrders(); System.out.println("=== Recent Orders ==="); // var in the for loop: 'currentOrder' is inferred as Order for (var currentOrder : recentOrders) { // var captures the return type of getCustomerName() — which is Optional<String> var maybeName = currentOrder.getCustomerName(); // var for a simple String built by orElse() var displayName = maybeName.orElse("Unknown Customer"); // var for LocalDate — no more writing LocalDate twice var placedOn = currentOrder.getOrderDate(); System.out.printf(" [#%d] %-15s $%.2f (placed: %s)%n", currentOrder.getOrderId(), displayName, currentOrder.getTotalAmount(), placedOn); } // var for a running total — inferred as double var grandTotal = recentOrders.stream() .mapToDouble(Order::getTotalAmount) .sum(); System.out.printf("%n Grand Total: $%.2f%n", grandTotal); } }
[#1001] Alice Martin $149.99 (placed: 2024-10-15)
[#1002] Bob Chen $89.50 (placed: 2024-10-15)
[#1003] Sara Lopez $220.00 (placed: 2024-10-15)
Grand Total: $459.49
Readability Tradeoffs — When var Helps and When It Hurts
var is a tool, not a religion. The Java community's consensus after years of using it is clear: var improves readability when the type is already obvious, and it damages readability when it isn't.
The core question to ask yourself every time is: 'Can a developer reading this line immediately tell what type this variable is without looking anywhere else?' If yes, var is probably fine. If no, spell the type out — your future self and your teammates will thank you.
var genuinely helps in three scenarios: long generic types like Map
var hurts in scenarios like capturing the result of a non-obvious method call, capturing a numeric literal that could be int, long, float, or double, and any public API — though Java already prevents var there by design. The comparison table below gives you a quick cheat sheet for making the call.
import java.util.*; import java.io.BufferedReader; import java.io.StringReader; public class VarReadabilityComparison { public static void main(String[] args) throws Exception { // ✅ GOOD USE of var — type is crystal clear from the right-hand side. // The word 'BufferedReader' would appear twice without var. var csvReader = new BufferedReader(new StringReader("id,name,price\n1,Keyboard,49.99")); // ✅ GOOD USE of var — long generic type, constructor makes it obvious. var inventoryByCategory = new HashMap<String, List<String>>(); inventoryByCategory.put("Electronics", new ArrayList<>(Arrays.asList("Keyboard", "Monitor"))); inventoryByCategory.put("Furniture", new ArrayList<>(Arrays.asList("Desk", "Chair"))); // ✅ GOOD USE of var in a for-each — 'entry' is clearly Map.Entry<String, List<String>> for (var entry : inventoryByCategory.entrySet()) { System.out.println("Category: " + entry.getKey() + " -> " + entry.getValue()); } System.out.println(); // ⚠️ QUESTIONABLE use of var — what type does 'result' hold? // A reader has to go find the getInventorySummary() signature. // In a real project, prefer writing the explicit return type here. var summary = getInventorySummary(inventoryByCategory); System.out.println("Summary: " + summary); // ❌ MISLEADING use of var — is this int? long? float? double? // A reader might assume int; it's actually long because of the L suffix. // Explicit type is clearer: long warehouseCapacity = 1_000_000L; var warehouseCapacity = 1_000_000L; System.out.println("Capacity type : " + ((Object) warehouseCapacity).getClass().getSimpleName()); System.out.println("Capacity value: " + warehouseCapacity); // Skip first header line and print the data line. var headerLine = csvReader.readLine(); // inferred: String var dataLine = csvReader.readLine(); // inferred: String System.out.println("\nCSV Header : " + headerLine); System.out.println("CSV Data : " + dataLine); csvReader.close(); } // Notice: return type is explicit here — var cannot be used for method return types. static String getInventorySummary(Map<String, List<String>> inventory) { var totalItems = inventory.values().stream() .mapToInt(List::size) .sum(); // inferred: int return totalItems + " items across " + inventory.size() + " categories"; } }
Category: Furniture -> [Desk, Chair]
Summary: 4 items across 2 categories
Capacity type : Long
Capacity value: 1000000
CSV Header : id,name,price
CSV Data : 1,Keyboard,49.99
| Aspect | Explicit Type Declaration | var (Type Inference) |
|---|---|---|
| Java version required | All Java versions | Java 10 and above only |
| Where it works | Everywhere — fields, params, return types, local vars | Local variables inside methods only |
| Type safety | Full static type safety | Full static type safety — identical |
| Runtime behavior | Type fixed at compile time | Type fixed at compile time — identical |
| JVM bytecode | Uses explicit type in bytecode | Uses explicit type in bytecode — var disappears after compilation |
| Can assign null at init | Yes: String name = null; | No — var name = null; is a compile error |
| Works with interfaces | Yes: List | Infers concrete class: ArrayList |
| Method parameters | Yes | Not allowed — compile error |
| Class-level fields | Yes | Not allowed — compile error |
| Readability (obvious type) | Verbose but explicit | Cleaner, less noise |
| Readability (non-obvious type) | Clear — type is right there | Can obscure intent — prefer explicit type here |
🎯 Key Takeaways
- var does not change Java's type system — the type is inferred once at compile time, locked permanently, and the JVM bytecode is byte-for-byte identical to an explicit declaration.
- var only works for local variables that are initialized on the same line — it cannot be used for class fields, method parameters, method return types, or constructor parameters.
- The biggest readability win for var is with verbose generic types like Map
> — it cuts noise without losing any type information. - The golden rule for using var: if a reader can tell the type immediately from the right-hand side without looking anywhere else, var is fine; if they have to go hunting, write the explicit type instead.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Declaring var without an initializer — Writing 'var orderCount;' on one line and assigning it on the next causes a compile error: 'cannot use var on variable without initializer'. Fix: always initialize var on the same line you declare it — 'var orderCount = 0;'.
- ✕Mistake 2: Using var with an untyped diamond operator — Writing 'var items = new ArrayList<>()' causes Java to infer ArrayList
- ✕Mistake 3: Assuming var works for method parameters or class fields — Writing 'public void process(var input)' or 'private var storeId = 1;' both produce compile errors. var is strictly for local variables. Fix: use explicit types in all method signatures, return types, parameters, and class-level fields — var has no place in a class's public contract.
Interview Questions on This Topic
- QWhat is the difference between Java's var keyword and JavaScript's var? A lot of developers confuse the two — can you explain what actually happens under the hood when the Java compiler encounters var?
- QCan you use var as a method return type or as a method parameter type in Java 10? Why or why not, and what does that design decision tell you about the intention behind the feature?
- QIf var removes type information, does that mean Java becomes dynamically typed when you use it? How would you explain to a colleague that var does not reduce type safety — and can you give a concrete example that proves it?
Frequently Asked Questions
Is Java var the same as JavaScript var?
No — they are completely different. JavaScript's var creates a dynamically typed, function-scoped variable whose type can change at runtime. Java's var is a compile-time shorthand: the type is inferred from the initializer, fixed permanently, and the compiled bytecode is identical to writing the explicit type yourself. Java never becomes dynamically typed.
Can var be used for instance variables (class fields) in Java?
No. Java explicitly disallows var for class-level fields, method parameters, method return types, and constructor parameters. It works only for local variables inside a method body, constructor body, or initializer block, and only when the variable is initialized on the same line it is declared.
Does using var in Java make the code harder to read?
It depends on context. When the right-hand side makes the type immediately obvious — like new ArrayList
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.