> ## Content Index
> Fetch the complete content index at: https://www.ggorantala.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Use var in Java Without Hurting Readability
- URL: https://www.ggorantala.dev/how-to-use-var-java/
- Published: 2026-08-05T05:26:00.000Z
- Updated: 2026-10-31T06:31:09.000Z
- Description: Learn how to use var in Java 10+ on a parcel tracker: where it helps, the diamond and ternary traps, 12 compiler errors forced on purpose, and 11 checks.
- Author: Gopi Gorantala
- Tags: how-to-tutorials, core-java, modern-java

## Before and after: the same report, two ways

Here's the report loop from the parcel tracker you'll build, written the old way and then with `var`. Both produce the same output, byte for byte, and the compiled code for the two is nearly identical:

```java
// before
for (Map.Entry<String, List<TrackingEvent>> entry : service.eventsByParcel().entrySet()) {
    List<TrackingEvent> history = entry.getValue();
    TrackingEvent last = history.get(history.size() - 1);
    ...
}

// after
for (var entry : service.eventsByParcel().entrySet()) {
    var history = entry.getValue();
    var last = history.get(history.size() - 1);
    ...
}

```

And what the finished app prints:

```log
P-1001 -> IN_TRANSIT @ Derby
P-1002 -> PICKED_UP @ York
total grams: 2000
true

```

That last `true` is the old and new report methods agreeing with each other.

If you're wondering how to use var in Java, the short answer is that it's a way to let the compiler fill in the type of a local variable from its initializer. The longer answer is a list of places where it quietly infers something you didn't mean. I'll walk through both, and every trap below was run, not guessed. This is for you if you know Java 8 and want to know when `var` is safe to reach for.

## Check your setup

| Tool  | Version I used                   | Check it with  |
| ----- | -------------------------------- | -------------- |
| JDK   | OpenJDK 21.0.12.1 (Ubuntu build) | java -version  |
| javac | 21.0.12.1                        | javac -version |

Anything from Java 10 on has `var`. Lambda parameters need Java 11\. I used no Maven, no Gradle and no test library; the checks are plain Java, so you can paste everything into a folder and compile it with the JDK alone. I'd normally reach for JUnit 5, and the next article in the series covers it. Here, I couldn't reach Maven Central from my build environment, so I kept it dependency-free.

I ran everything on Linux. On Windows, the commands are the same except for the path separators in the `javac` line.

## Create the parcel tracker project

You need a folder and one package. That's all.

```log
parcels/
└── src/com/example/parcels/
    ├── Parcel.java
    ├── TrackingEvent.java
    ├── ParcelService.java
    ├── Before.java
    ├── After.java
    ├── TreeSortedReport.java
    ├── Traps.java
    ├── Main.java
    └── Checks.java

```

**From the command line.** `mkdir -p parcels/src/com/example/parcels && cd parcels`.

**In IntelliJ IDEA or Eclipse.** Create a plain Java project (no build tool), set the project language level to 21, and make `src` the source folder. I didn't capture exact menu paths for either IDE in this run, so use your IDE's help for "new Java project" rather than trusting a path from me. Both show the inferred type of a `var` when you hover over the variable, and that's worth turning to constantly while you read the traps below.

## Put var to work, one step at a time

### Step 1: Write the domain records

Goal: two small records the rest of the code uses.

`src/com/example/parcels/Parcel.java`

```java
package com.example.parcels;

import java.time.Instant;

public record Parcel(String id, String carrier, int weightGrams, Instant shippedAt) {
}

```

`src/com/example/parcels/TrackingEvent.java`

```java
package com.example.parcels;

import java.time.Instant;

public record TrackingEvent(String parcelId, String status, String city, Instant at) {
}

```

Why records: they keep the example short, and the accessor names (`id()`, `status()`) are what `var` will infer types from later. Records are covered in [how to use Java records](https://ggorantala.dev/how-to-use-java-records/?ref=ggorantala.dev).

### Step 2: Write the service

Goal: a tiny in-memory store with something to loop over.

`src/com/example/parcels/ParcelService.java`

```java
package com.example.parcels;

import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ParcelService {

    private final List<Parcel> parcels = new ArrayList<>();
    private final Map<String, List<TrackingEvent>> events = new HashMap<>();

    public void register(Parcel parcel) {
        parcels.add(parcel);
        events.put(parcel.id(), new ArrayList<>());
    }

    public void record(String parcelId, String status, String city, Instant at) {
        events.get(parcelId).add(new TrackingEvent(parcelId, status, city, at));
    }

    public List<Parcel> allParcels() {
        return List.copyOf(parcels);
    }

    public Map<String, List<TrackingEvent>> eventsByParcel() {
        return events;
    }

    public int totalWeight() {
        int total = 0;
        for (Parcel p : parcels) {
            total += p.weightGrams();
        }
        return total;
    }
}

```

Look at the field declarations. `private final List<Parcel> parcels` keeps its explicit type because fields can't use `var` (you'll see that error shortly). The `new ArrayList<>()` on the right is allowed to infer its type argument because the left side says what it is.

Checkpoint: `javac -d out src/com/example/parcels/Parcel.java src/com/example/parcels/TrackingEvent.java src/com/example/parcels/ParcelService.java` should print nothing. No output means it compiled.

### Step 3: Write the report both ways

Goal: the same method, before and after.

`src/com/example/parcels/Before.java`

```java
package com.example.parcels;

import java.util.List;
import java.util.Map;

/** The same report, written the pre-Java-10 way. */
public class Before {
    public static String report(ParcelService service) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, List<TrackingEvent>> entry : service.eventsByParcel().entrySet()) {
            List<TrackingEvent> history = entry.getValue();
            TrackingEvent last = history.get(history.size() - 1);
            sb.append(entry.getKey()).append(" -> ").append(last.status()).append('\n');
        }
        return sb.toString();
    }
}

```

`src/com/example/parcels/After.java`

```java
package com.example.parcels;

public class After {
    public static String report(ParcelService service) {
        var sb = new StringBuilder();
        for (var entry : service.eventsByParcel().entrySet()) {
            var history = entry.getValue();
            var last = history.get(history.size() - 1);
            sb.append(entry.getKey()).append(" -> ").append(last.status()).append('\n');
        }
        return sb.toString();
    }
}

```

Notice that `After` has no imports. `Map.Entry` and `List` never get named, so there's nothing to import. That's the real payoff: the long generic type (`Map.Entry<String, List<TrackingEvent>>`) was repeating something the right-hand side already says.

I use `var` in exactly these three places: `new SomeType(...)` on the right, a long generic type that the call already shows, and the loop variable in a `for` over a typed collection. Everywhere else I ask whether a reader can see the type without hovering.

`src/com/example/parcels/TreeSortedReport.java`

```java
package com.example.parcels;

import java.util.TreeMap;

public class TreeSortedReport {
    public String render(ParcelService service) {
        var sorted = new TreeMap<>(service.eventsByParcel());
        var sb = new StringBuilder();
        sorted.forEach((id, history) -> {
            var last = history.get(history.size() - 1);
            sb.append(id).append(" -> ").append(last.status()).append(" @ ").append(last.city()).append('\n');
        });
        return sb.toString();
    }
}

```

Here `new TreeMap<>(otherMap)` infers `TreeMap<String, List<TrackingEvent>>` from its constructor argument. That's the diamond working as it should, because the argument carries the type information. Hold that thought for Step 4.

### Step 4: Write the traps class

Goal: one method per surprise, so you can run each and look at what `var` did.

`src/com/example/parcels/Traps.java`

```java
package com.example.parcels;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;

public class Traps {

    public static String diamondTrap() {
        var list = new ArrayList<>();      // ArrayList<Object>, not ArrayList<String>
        list.add("P-1");
        list.add(42);                      // compiles: the element type is Object
        return list.toString();
    }

    public static String diamondFixed() {
        var list = new ArrayList<String>();
        list.add("P-1");
        // list.add(42);                   // would not compile
        return list.toString();
    }

    public static String concreteType() {
        var ids = new ArrayList<String>(); // type is ArrayList<String>, not List<String>
        ids.ensureCapacity(100);           // ArrayList-only method compiles
        List<String> asList = ids;         // what you wrote before var
        return ids.getClass().getSimpleName() + "/" + asList.size();
    }

    public static String literalTypes() {
        var a = 1;       // int
        var b = 1L;      // long
        var c = 1.0;     // double
        var d = 1.0f;    // float
        var e = 'x';     // char
        var f = 100_000 * 100_000; // int overflow, still int
        return Integer.valueOf(a).getClass().getSimpleName() + ","
                + Long.valueOf(b).getClass().getSimpleName() + ","
                + Double.valueOf(c).getClass().getSimpleName() + ","
                + Float.valueOf(d).getClass().getSimpleName() + ","
                + Character.valueOf(e).getClass().getSimpleName() + ","
                + f;
    }

    public static String ternaryTrap(boolean flag) {
        var x = flag ? 1 : "one";   // an intersection type of Serializable and Comparable
        return x.getClass().getSimpleName();
    }

    public static int anonymousClass() {
        var counter = new Object() {
            int hits = 0;
            void hit() { hits++; }
        };
        counter.hit();
        counter.hit();
        return counter.hits;            // only possible with var
    }

    public static int lambdaParams() {
        BiFunction<Integer, Integer, Integer> add = (var x, var y) -> x + y;
        return add.apply(2, 3);
    }

    public static String varAsName() {
        var var = "legal";            // var is not a keyword; this compiles
        return var;
    }
}

```

I want to spend a moment on three of these.

**The diamond trap.** `var list = new ArrayList<>()` is the single most common `var` mistake I see in code review. With a typed declaration the diamond picks up `String` from the left. With `var` there's no left, so the compiler falls back to `Object`. It compiles, `list.add(42)` works, and you've lost the type check that made generics worth having. The fix is to put the type argument on the right: `new ArrayList<String>()`.

**You get the concrete type, not the interface.** `var ids = new ArrayList<String>()` is an `ArrayList`, not a `List`. That's why `ensureCapacity` compiles. Sometimes that's fine. If you wanted to program to the interface, write `List<String> ids = new ArrayList<>()` and keep the explicit type. I would.

**Anonymous classes.** This is the one thing `var` can do that an explicit type can't: `counter.hits` is reachable because the variable's type is the anonymous class itself. You can't name that type, so before Java 10 you couldn't hold onto it. I've needed this about twice in real code. Don't go looking for excuses to use it.

Checkpoint: compile what you have so far (`Main.java` and `Checks.java` come next, so list the files by name):

```bash
mkdir -p out
javac --release 21 -d out src/com/example/parcels/{Parcel,TrackingEvent,ParcelService,Before,After,TreeSortedReport,Traps}.java

```

It printed nothing for me.

### Step 5: Write Main

`src/com/example/parcels/Main.java`

```java
package com.example.parcels;

import java.time.Instant;

public class Main {
    public static void main(String[] args) {
        var service = new ParcelService();
        var t0 = Instant.parse("2026-10-20T08:00:00Z");
        service.register(new Parcel("P-1001", "DHL", 1200, t0));
        service.register(new Parcel("P-1002", "UPS", 800, t0));
        service.record("P-1001", "PICKED_UP", "Leeds", t0);
        service.record("P-1001", "IN_TRANSIT", "Derby", t0.plusSeconds(3600));
        service.record("P-1002", "PICKED_UP", "York", t0);
        System.out.print(new TreeSortedReport().render(service));
        System.out.println("total grams: " + service.totalWeight());
        System.out.println(Before.report(service).equals(After.report(service)));
    }
}

```

## See it working

```bash
mkdir -p out
javac --release 21 -d out src/com/example/parcels/*.java
java -cp out com.example.parcels.Main

```

```log
P-1001 -> IN_TRANSIT @ Derby
P-1002 -> PICKED_UP @ York
total grams: 2000
true

```

If you see `true` on the last line, the `var` version and the explicit version agree. The whole run finished in 0.07 seconds on my machine, which is the JVM starting up, not `var` costing anything.

## Write the checks

I'd normally use JUnit 5 here. Since this build had no route to Maven Central, these are plain checks with a `main` method that exits non-zero on failure. They test the same things a JUnit class would.

`src/com/example/parcels/Checks.java`

```java
package com.example.parcels;

import java.time.Instant;

public class Checks {
    static int passed = 0, failed = 0;

    static void check(String name, boolean ok) {
        if (ok) { passed++; System.out.println("PASS  " + name); }
        else { failed++; System.out.println("FAIL  " + name); }
    }

    public static void main(String[] args) {
        var service = new ParcelService();
        var t0 = Instant.parse("2026-10-20T08:00:00Z");
        service.register(new Parcel("P-1", "DHL", 1200, t0));
        service.register(new Parcel("P-2", "UPS", 800, t0));
        service.record("P-1", "PICKED_UP", "Leeds", t0);
        service.record("P-2", "PICKED_UP", "York", t0);

        check("Before and After produce the same report", Before.report(service).equals(After.report(service)));
        check("total weight is 2000 grams", service.totalWeight() == 2000);
        check("diamond trap accepts an Integer", Traps.diamondTrap().equals("[P-1, 42]"));
        check("explicit type argument gives ArrayList<String>", Traps.diamondFixed().equals("[P-1]"));
        check("var keeps the concrete type ArrayList", Traps.concreteType().equals("ArrayList/0"));
        check("literal types", Traps.literalTypes().equals("Integer,Long,Double,Float,Character,1410065408"));
        check("ternary with mixed types compiles (Integer)", Traps.ternaryTrap(true).equals("Integer"));
        check("ternary with mixed types compiles (String)", Traps.ternaryTrap(false).equals("String"));
        check("anonymous class members reachable through var", Traps.anonymousClass() == 2);
        check("var in lambda parameters", Traps.lambdaParams() == 5);
        check("var can be a variable name", Traps.varAsName().equals("legal"));

        System.out.println(passed + " passed, " + failed + " failed");
        if (failed > 0) System.exit(1);
    }
}

```

```bash
javac --release 21 -d out src/com/example/parcels/*.java
java -cp out com.example.parcels.Checks

```

```log
PASS  Before and After produce the same report
PASS  total weight is 2000 grams
PASS  diamond trap accepts an Integer
PASS  explicit type argument gives ArrayList<String>
PASS  var keeps the concrete type ArrayList
PASS  literal types
PASS  ternary with mixed types compiles (Integer)
PASS  ternary with mixed types compiles (String)
PASS  anonymous class members reachable through var
PASS  var in lambda parameters
PASS  var can be a variable name
11 passed, 0 failed

```

The odd number in the literal check, `1410065408`, is `100_000 * 100_000` overflowing an `int`. `var f = 100_000 * 100_000` is an `int`, and nothing warns you. Write `100_000L * 100_000` if you wanted a `long`.

## What the compiler does with var

`var` isn't a type. It's an instruction to the compiler: look at the initializer, work out its type, and write that type in for me. It happens once, at compile time. The class file doesn't know `var` existed.

I checked this with `javap`. For `List<String> l = new ArrayList<String>()` the bytecode calls `List.add` through `invokeinterface`. For `var l = new ArrayList<String>()` it calls `ArrayList.add` through `invokevirtual`. That one line is the whole difference, and it comes from the type being `ArrayList` instead of `List`, not from `var` itself.

Because the type comes from the right-hand side, three things follow:

- No initializer, no `var`. `var x;` has nothing to infer from.
- `null`, an array initializer `{1,2,3}`, a lambda, and a method reference all lack a type of their own, so none of them work either.
- The variable's type is fixed after that. `var x = 1; x = "s";` fails because `x` is an `int`.

`var` is also a *restricted type name*, not a keyword. That's why `var var = "legal"` compiles and `class var {}` doesn't. Code from before Java 10 that used `var` as a variable or method name still compiles.

For the same idea in the context of other recent additions, see [how to use sealed classes and interfaces](https://ggorantala.dev/how-to-use-sealed-classes-interfaces-java/?ref=ggorantala.dev), and for `var` inside a stream pipeline see [how to filter and transform a list with Java streams](https://ggorantala.dev/how-to-filter-transform-list-java-streams/?ref=ggorantala.dev).

## Compiler messages you'll meet

I forced each of these on purpose. Cause first, then the cure.

`**cannot use 'var' on variable without initializer**` (`var x; x = 1;`) means the compiler had nothing to infer from. Initialize it on the declaration line, or write the type.

`**variable initializer is 'null'**` (`var x = null;`) means `null` has no type of its own. Write the type.

`**array initializer needs an explicit target-type**` (`var x = {1,2,3};`) means the braces alone don't say whether this is an `int[]` or a `long[]`. Use `var x = new int[]{1, 2, 3};`.

`**lambda expression needs an explicit target-type**` (`var f = () -> 1;`) means a lambda needs a functional interface to be a lambda *of*. Declare it as `Supplier<Integer> f = () -> 1;`.

`**'var' is not allowed here**` appeared for a field (`var field = 1;`) and for a method return type (`static var m()`). `var` is for local variables, `for` loop variables, try-with-resources variables and lambda parameters. Fields and method signatures are public contracts, and the language wants those written out.

`**'var' is not allowed in a compound declaration**` (`var x = 1, y = 2;`). One `var` per declaration.

`**incompatible types: String cannot be converted to int**` (`var x = 1; x = "s";`) is the compiler doing its job. The variable is an `int`.

`**incompatible types: possible lossy conversion from double to int**` (`var x = 10; x = 3.5;`) is the same lesson with numbers. If you want a `double`, initialize with `10.0` or write `double`.

`**'var' not allowed here ... restricted type name**` (`class var {}`) means you can't name a type `var` any more.

`**cannot mix 'var' and implicitly-typed parameters**` (`(var x, y) -> x + y`) means in a lambda, either every parameter uses `var` or none does. Mixing them is out. The reason to write `(var x, var y)` at all is to attach an annotation to the parameter.

`**cannot find symbol class var**` is what you get if you compile with `--release 9`. Java 9 doesn't know `var`, so it treats it as a missing class. I checked: `--release 10` compiles the same file cleanly.

`**variable s is already defined**` (two `var s` in one scope) is ordinary Java. Mentioning it because `var` doesn't give you a second chance to redeclare.

### One that I expected to fail silently, and didn't

I wanted to show `var` hiding a change in return type. I wrote `var kg = weightKg();` where `weightKg()` returned `int`, then changed it to return `double`, with a following line `int rounded = kg / 2;`. I expected it to compile and quietly change behaviour. It didn't: the explicit `int rounded` on the next line caught it, with `possible lossy conversion from double to int`. So `var` didn't hide the change here, because something downstream still named a type. If *everything* downstream also used `var`, nothing would complain, and the behaviour would change without an error. Your tests are what catch that, not the compiler.

Then there's integer division. I ran this:

```java
var grams = 5; var parcels = 2;
var avg = grams / parcels;               // int
double avg2 = grams / (double) parcels;  // double

```

and it printed `2 2.5`. Nothing about that is specific to `var`, but with an explicit `double avg` you'd have stopped and thought about the cast. With `var` the declaration doesn't prompt you.

A last small oddity from `Mixed.java`: `var y = (a.length == 0) ? 1 : 2.5;` printed `1.0`. A ternary with an `int` and a `double` promotes both to `double`, so `y` is a `double` and the `1` became `1.0`. And `var x = true ? 1 : "one"` isn't an `Object` but an intersection type; I confirmed `x` held an `Integer` at runtime.

## Team rules worth writing down

Here's how I'd decide, using what the build above showed.

Use `var` when the right-hand side names the type (`new StringBuilder()`, `new ParcelService()`, `Instant.parse(...)`), when the type is a long generic you'd otherwise repeat, and for loop variables over a typed collection.

Keep the explicit type when the initializer is a method call whose return type isn't obvious from its name, when you want to program to an interface (`List<String>`, not `ArrayList<String>`), when you're working with numeric literals and the exact primitive type matters, and in anything that's a field, parameter or return type, where `var` isn't allowed anyway.

Never write `var x = new ArrayList<>();` and `var x = flag ? 1 : "one";`.

Name variables well. Without a visible type, `var data = load();` tells the reader nothing, and `var overdueParcels = load();` tells them plenty. Most `var` complaints in code review are really naming complaints.

Style guides disagree on how aggressively to use it. The official OpenJDK style guidelines for `var` (JEP 286 and the local-variable type inference guide) lean cautious, and I'd follow them: a reader should never need an IDE to know what a variable is.

I didn't check exact IDE menu paths for showing inferred types or for converting between `var` and explicit types in this run, so I'm not going to invent them. Search your IDE's settings for "var" and you'll find both.

## Checklist

- You can use `var` for local variables, `for` and for-each variables, try-with-resources variables and lambda parameters (Java 11+). You can't use it for fields, parameters or return types.
- The initializer decides the type. It must exist, and can't be `null`, a lambda, a method reference or a bare array initializer.
- `new ArrayList<>()` with `var` gives you `ArrayList<Object>`. Put the type argument on the right.
- `var` gives you the concrete type on the right, not the interface you may have wanted.
- The compiled code is the same as with an explicit type. The only difference is which type the compiler picked.

## Questions people ask about var

**Is `var` the same as `Object` or dynamic typing?** No. The type is fixed at compile time. `var x = 1; x = "s";` doesn't compile because `x` is an `int`.

**Can I use `var` in Java 8?** No. It arrived in Java 10, and `var` in lambda parameters in Java 11\. Compiling with `--release 9` gives `cannot find symbol class var`.

**Does `var` make code slower or use more memory?** No. I compared the bytecode for `List<String> l = new ArrayList<String>()` and `var l = new ArrayList<String>()`: the same `new` and constructor call, and the only change was `invokeinterface List.add` versus `invokevirtual ArrayList.add`.

**Can `var` be used for fields or method return types?** No. Both produce `'var' is not allowed here`.

**Can a variable still be named `var`?** Yes, `var var = "legal"` compiles. You can't name a class `var`.

## References

- [JEP 286: Local-Variable Type Inference](https://openjdk.org/jeps/286?ref=ggorantala.dev)
- [JEP 323: Local-Variable Syntax for Lambda Parameters](https://openjdk.org/jeps/323?ref=ggorantala.dev)
- [Local Variable Type Inference: Style Guidelines](https://openjdk.org/projects/amber/guides/lvti-style-guide?ref=ggorantala.dev)
- [Java Language Specification, section 14.4: Local Variable Declarations](https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html?ref=ggorantala.dev#jls-14.4)