Skip to content
Java modern java 7 min read

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: var is NOT a keyword — it is a context-sensitive reserved type name. That means you can still name a variable, method, or package var (though you really should not). Existing code that uses var as 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:

ContextAllowed?
Local variable with initializerYes
Enhanced for-loop variableYes
try-with-resources variableYes
Method parameterNo
Constructor parameterNo
Method return typeNo
Field declarationNo
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 <> with var infers Object as the type argument. Always provide the type argument on the right-hand side when using var with 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 var for ALL parameters or NONE. You cannot mix var with 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 var infers, write the type explicitly. If the right-hand side makes it obvious (a constructor call, a literal, a factory method), var is a good fit.

Here is a quick decision guide:

SituationUse var?
Constructor call: new ArrayList<String>()Yes
Obvious literal: 0, "hello", trueYes
Long, verbose generic typeYes
Opaque method return valueAvoid
Return type or fieldNot allowed
Code shared with beginners who find it confusingAvoid

Under the Hood

var is purely a compile-time feature. Here is what actually happens when you write var x = someExpression:

  1. The Java compiler evaluates the type of someExpression using standard type inference rules.
  2. It replaces var with the inferred concrete type in the internal AST (Abstract Syntax Tree).
  3. The compiled bytecode contains exactly the same LOAD/STORE instructions as if you had written the explicit type yourself.
  4. The LocalVariableTable attribute in the .class file 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

  • var requires Java 10 or later (JEP 286).
  • Lambda var parameters require Java 11 or later (JEP 323).
  • All modern IDEs (IntelliJ IDEA, Eclipse, VS Code with Java extensions) fully support var with type hints on hover — you always know what the inferred type is.
  • Static analysis tools like Checkstyle let you configure rules about when var is allowed (e.g., restrict to local variables only, enforce explicit types for non-obvious initializers).
  • Variables — The fundamentals of declaring and using variables in Java before var came along.
  • Data Types — Understanding Java’s type system helps you reason about what var infers.
  • Java 8 Features — Lambda expressions, streams, and functional interfaces — the foundation var builds on.
  • Lambda Expressions — Java 11 extended var to lambda parameters, making annotations on parameters possible.
  • Generics — Understanding generics is essential for using var safely with collections.
  • Modern Java (9–21) — Overview of all major Java improvements since Java 8, including records, sealed classes, and virtual threads.
Last updated June 13, 2026
Was this helpful?