Back to Variables, Scope & var
Q3Question 3 of 3

What is the var keyword in Java?

Key Concepts
  • var is local variable type inference, introduced in Java 10 – the compiler infers the type from the initializer
  • The variable is still statically typed at compile time – var is NOT dynamic typing like JavaScript or Python
  • Allowed for: local variables with initializer, for-loop index, for-each element, try-with-resources
  • NOT allowed for: fields, method parameters, return types, catch parameters, or var x = null
  • The generated bytecode is identical to writing the explicit type – zero runtime cost
  • Use var when the right-hand side makes the type obvious; keep explicit types when it aids readability

Definition

var lets the compiler infer a local variable's type from its initializer at compile time. It is shorthand for the full type, not dynamic typing.

Three facts define var:

  • Inference, not magic. When you write var account = new Account("SAV2001"), the compiler reads the right-hand side and substitutes Account as the type. The bytecode is identical to writing Account account = new Account("SAV2001").
  • Local only. var works for local variables, for-loop variables, for-each variables, and try-with-resources variables. It does not work for fields, parameters, or return types.
  • Still static. The type is fixed at compile time. You cannot later assign an incompatible type. This is type inference, not dynamic typing.

When you fill a bank form, instead of writing "Savings Account" in the account type box, you attach the passbook and let the clerk read the type from it. The clerk stamps the exact type permanently. If you attach a savings passbook, the form is stamped "Savings" forever. You cannot later swap in a loan document. The clerk also refuses to stamp if you attach nothing. That is var: the compiler reads the type from what you assign, stamps it permanently, and refuses if there is nothing to read from.

Understanding the Concepts

What is var?

var was introduced in Java 10 as local variable type inference (JEP 286). It lets you declare a local variable without writing the type on the left side. The compiler infers the type from the expression on the right.

JAVA
// Without var: type written twice
ArrayList<String> names = new ArrayList<String>();

// With var: type written once, compiler infers ArrayList<String>
var names = new ArrayList<String>();

Both lines produce identical bytecode. var is purely a compile-time convenience.

Why does var exist?

Java code often repeats long type names. Before var, you had to write the full type on both sides of the assignment:

JAVA
// Verbose: the type appears twice
Map<String, List<BigDecimal>> accountTransactions = new HashMap<String, List<BigDecimal>>();

// With var: clean and readable, type obvious from the right side
var accountTransactions = new HashMap<String, List<BigDecimal>>();

var removes the noise on the left while keeping the full type information on the right. It is especially useful for:

  • Long generic types like Map>
  • Constructor calls where the type name appears in the new expression
  • Factory methods like List.of(...) or Map.entry(...) where the return type is obvious

Where can var be used?

var is allowed in exactly four positions:

1. Local variable with an initializer

JAVA
public void processLoan() {
    var principal = new BigDecimal("500000");     // infers BigDecimal
    var rate = 8.5;                                // infers double
    var months = 60;                               // infers int
    var processor = new LoanProcessor();           // infers LoanProcessor
}

2. For-loop index variable

JAVA
for (var i = 0; i < accounts.size(); i++) {   // infers int
    var account = accounts.get(i);             // infers Account
    System.out.println(account.getBalance());
}

3. For-each loop element

JAVA
List<Account> accounts = getAccounts();
for (var account : accounts) {   // infers Account from List<Account>
    account.credit(interest);
}

4. Try-with-resources variable

JAVA
try (var reader = new BufferedReader(new FileReader("accounts.csv"))) {
    var line = reader.readLine();   // infers String
    System.out.println(line);
}

Where can var NOT be used?

var is illegal in these positions, and each has a clear reason:

JAVA
// FIELD: var not allowed – fields are type contracts
public class Account {
    // var balance = BigDecimal.ZERO;  // COMPILE ERROR
    private BigDecimal balance = BigDecimal.ZERO;  // must be explicit
}

// METHOD PARAMETER: var not allowed – signatures are contracts
// public void debit(var amount) {}  // COMPILE ERROR
public void debit(BigDecimal amount) {}  // must be explicit

// RETURN TYPE: var not allowed
// public var getBalance() {}  // COMPILE ERROR
public BigDecimal getBalance() { return balance; }

// NO INITIALIZER: nothing to infer from
// var x;  // COMPILE ERROR

// NULL: null has no specific type
// var x = null;  // COMPILE ERROR

// ARRAY INITIALIZER without new: no standalone type
// var arr = {1, 2, 3};  // COMPILE ERROR
var arr = new int[]{1, 2, 3};  // OK: infers int[]

The common theme: var needs something on the right with a clear, specific type. Fields, parameters, and return types are part of a class's public API and must be explicit for readability and binary compatibility.

Why is var still statically typed?

This is the most important point interviewers test. var does not make Java dynamic.

JAVA
var name = "Pankaj";   // type is String, fixed at compile time
// name = 42;           // COMPILE ERROR: int cannot be assigned to String
// name = new Account(); // COMPILE ERROR: Account cannot be assigned to String

Compare this with JavaScript, which is truly dynamic:

TEXT
// JavaScript (dynamic typing)
let name = "Pankaj";
name = 42;          // OK in JavaScript – type changes at runtime
name = { id: 1 };   // OK in JavaScript – type changes again

In Java with var, the type is decided once at compile time and never changes. The bytecode is identical to writing the explicit type. There is zero runtime overhead.

How is var different from Kotlin's val and var?

Kotlin has two keywords: val (immutable) and var (mutable). Both infer types.

| Feature | Java var | Kotlin val | Kotlin var |
|---|---|---|---|
| Type inference | Yes | Yes | Yes |
| Mutable | Yes | No (read-only) | Yes |
| Works on fields | No | Yes | Yes |
| Works on parameters | No | No | No |
| Statically typed | Yes | Yes | Yes |

Java's var is always mutable. If you want immutability in Java, combine final and var:

JAVA
final var accountId = "SAV2001";   // inferred String, cannot be reassigned
// accountId = "NEW001";           // COMPILE ERROR: final variable

What about var in lambda parameters (Java 11)?

Java 11 (JEP 323) added the ability to use var in lambda parameter declarations. This is useful when you need to add annotations to lambda parameters:

JAVA
// Without var: cannot add annotations to implicit lambda params
list.stream()
    .filter(account -> account.getBalance().signum() > 0)
    .toList();

// With var (Java 11): now you can add @NonNull annotation
list.stream()
    .filter((@NonNull var account) -> account.getBalance().signum() > 0)
    .toList();

Without var, there is no syntax position to attach the annotation. var in lambda parameters exists specifically to enable annotations.

What are the edge cases?

Edge case 1: Diamond with var infers Object

JAVA
// WRONG: diamond <> with var infers ArrayList<Object>, not ArrayList<String>
var list = new ArrayList<>();
list.add("Rajesh");
list.add(42);        // compiles! because list is ArrayList<Object>

// CORRECT: write the type argument on the right
var list = new ArrayList<String>();
list.add("Rajesh");
// list.add(42);     // COMPILE ERROR: int cannot be added to ArrayList<String>

Edge case 2: Numeric literal inference

JAVA
var a = 10;       // int (not Integer)
var b = 10L;      // long
var c = 10.0;     // double (not float)
var d = 10.0f;    // float
var e = 'A';      // char
var f = true;     // boolean

The inferred type follows the literal rules, not your intention. If you want a long, you must write the L suffix.

Edge case 3: Ternary operator infers intersection type

JAVA
var x = condition ? "hello" : 42;
// Inferred type: Serializable & Comparable<...>
// This is a legal but confusing type that you probably did not want

When the two branches have different types, the compiler finds the most specific common supertype. The result is often an intersection type that is hard to work with. Avoid using var with ternary expressions that return different types.

Edge case 4: var captures anonymous class types

var can infer a type that you cannot write explicitly:

JAVA
var account = new Object() {
    String id = "SAV2001";
    BigDecimal balance = new BigDecimal("1000000");
};

// This works! The anonymous type has the fields
System.out.println(account.id);       // SAV2001
System.out.println(account.balance);  // 1000000

Without var, the reference type would be Object, and you could not access id or balance. With var, the compiler infers the anonymous type, which includes the fields. This is a curiosity, not a recommended pattern.

What are common mistakes?

Mistake 1: Assuming var is dynamic

JAVA
// WRONG thinking: "var means I can change the type later"
var balance = new BigDecimal("1000");
// balance = "one thousand";  // COMPILE ERROR: String cannot be assigned to BigDecimal

Mistake 2: Using diamond without type arguments

JAVA
// WRONG: infers HashMap<Object, Object>
var map = new HashMap<>();

// CORRECT: infers HashMap<String, BigDecimal>
var map = new HashMap<String, BigDecimal>();

Mistake 3: Using var where the type is not obvious

JAVA
// BAD: what type is result? Reader cannot tell without checking the method
var result = service.process(accountId);

// GOOD: explicit type helps the reader
TransferResult result = service.process(accountId);

Mistake 4: Trying var on a field

JAVA
public class Account {
    // var balance = BigDecimal.ZERO;  // COMPILE ERROR: var not allowed on fields
    private BigDecimal balance = BigDecimal.ZERO;  // correct
}

Mistake 5: Trying var with null

JAVA
// var account = null;  // COMPILE ERROR: cannot infer type from null
Account account = null;  // correct: explicit type, null value

When should you use var?

Use var when:

  • The right-hand side is a constructor call: var account = new Account(...) – the type is obvious.
  • The right-hand side is a factory method with a clear name: var accounts = List.of(a, b, c).
  • The type name is long and repeated: var txnMap = new HashMap>().

When should you NOT use var?

Avoid var when:

  • The right-hand side is a method call with an unclear return type: var x = dao.find(id) – the reader must look up the method.
  • You want an interface type but var infers the implementation: var list = new ArrayList<>() infers ArrayList, not List. If you want the interface, write List list = new ArrayList<>().
  • The variable is passed to many methods and the type matters for understanding the code flow.

How do IDEs and tools help?

  • IntelliJ IDEA shows the inferred type as a gray hint next to the var declaration. You can hover over var to see the full type.
  • Eclipse shows the inferred type in a tooltip on hover.
  • SonarQube has rules like java:S6212 that suggest using var where appropriate, and rules that warn against using var where it hurts readability.
  • javac with -Xlint does not warn about var usage, but IDEs provide real-time feedback.

What should a senior engineer know?

  • var is a reserved type name, not a keyword. You can still have a variable named var (though it is confusing). You cannot have a class named var.
  • var does not affect binary compatibility. Since the bytecode is identical, adding or removing var in source does not change the compiled output.
  • var in lambda parameters (Java 11) exists for annotations. Without it, there is no syntax position for @NonNull or @Nullable on implicit lambda params.
  • IntelliJ can convert between var and explicit types in one click. Use Refactor > Type Migration or the intention action.
  • Team style guides should define when to use var. Google's Java style guide allows var for obvious types. Some teams ban it entirely. Consistency matters more than individual preference.
  • var cannot express wildcards. You cannot write var list = getList() and have the type be List. The inferred type will be the concrete type, which may be more specific than you want.

Visual Overview

DIAGRAM
  ── var: compile-time inference flow ──

  Source code:   var account = new Account("SAV2001");
                      │
                      │ compiler reads RHS type
                      ▼
  Inferred type: Account account = new Account("SAV2001");
                      │
                      │ javac emits bytecode
                      ▼
  Bytecode:      identical to explicit Account declaration
                 (no runtime difference, no overhead)


  ── Where var is allowed ──

  var x = expr;             local with initializer       ✓
  for (var i = 0; ...)      for-loop index               ✓
  for (var e : list)        for-each element              ✓
  try (var r = open())      try-with-resources            ✓

  ── Where var is NOT allowed ──

  class field               part of type contract         ✗
  method parameter          part of signature contract    ✗
  return type               part of signature contract    ✗
  catch parameter           must be explicit              ✗
  var x;                    no initializer to infer from  ✗
  var x = null;             null has no specific type     ✗


  ── var vs dynamic typing ──

  Java:       var s = "hi"; s = 42;     → COMPILE ERROR (static)
  JavaScript: let s = "hi"; s = 42;     → OK (dynamic)
  Python:     s = "hi"; s = 42          → OK (dynamic)
  Kotlin:     var s = "hi"; s = 42      → COMPILE ERROR (static, like Java)

Key Takeaway

var, introduced in Java 10, is local variable type inference. When you write var account = new Account("SAV2001"), the compiler reads the right-hand side, infers the type as Account, and substitutes it. The bytecode is identical to writing the explicit type. There is zero runtime cost.

var is allowed in four places: local variables with an initializer, for-loop index variables, for-each loop elements, and try-with-resources variables. It is not allowed on fields, method parameters, return types, or catch parameters, because those are part of a type contract that must be explicit. It also cannot be used without an initializer or with null, because the compiler has nothing to infer from.

The most important thing to remember is that var is not dynamic typing. The type is fixed at compile time and never changes. If var infers String, you cannot later assign an int. This is fundamentally different from JavaScript or Python, where a variable can hold any type. Java's var is type inference, not type flexibility.

In our banking codebase, var cleans up verbose declarations like Map> where the constructor already states the type. But we keep explicit types on fields and method signatures because those are contracts, and we keep explicit types on locals where the right-hand side is a method call with an unclear return type. We also always write the type argument on the right of the diamond when using var, because var list = new ArrayList<>() infers ArrayList, which is almost never what you want.

One sentence to remember: var infers a local variable's type from its initializer at compile time, produces identical bytecode, and is still fully statically typed.

---

Share
Use ← → arrow keys to navigate