⚡IntermediateModern Java

var and Local Type Inference

Use the var keyword (Java 10+) for local variable type inference to reduce verbosity without sacrificing type safety.

Overview

Java 10 introduced the var keyword for local variable type inference. When you write var x = new ArrayList<String>(), the compiler infers the type ArrayList<String> and treats x exactly as if you had written that type explicitly. var is not dynamic typing: the type is fixed at compile time. var reduces verbosity, especially with long generic types or when the right-hand side already makes the type obvious. It is only allowed for local variables with an initializer, not for fields, method parameters, or return types.

Key Points

  • •var can only be used for local variables that have an initializer
  • •The type is inferred at compile time; it is statically typed
  • •var cannot be used for fields, method parameters, or return types
  • •var is not allowed when the type is not obvious: var x = null; is a compile error
  • •Use var when the type is clear from the right-hand side
  • •Avoid var when the type adds important context that is not obvious
  • •var works well in for loops, try-with-resources, and long generic types

Examples

var for common local variables

import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Instead of: ArrayList<String> names = new ArrayList<>();
        var names = new ArrayList<String>();
        names.add("Alice");
        names.add("Bob");

        // Instead of: Map<String, List<Integer>> grouped = new HashMap<>();
        var grouped = new HashMap<String, List<Integer>>();
        grouped.put("even", List.of(2, 4, 6));
        grouped.put("odd",  List.of(1, 3, 5));

        // var in for-each: the type is still String
        for (var name : names) {
            System.out.println(name.toUpperCase()); // name is String
        }

        // var in try-with-resources
        try (var scanner = new Scanner(System.in)) {
            // scanner is Scanner
        } catch (Exception e) {}

        // var with literal: type is inferred
        var count = 42;        // int
        var price = 9.99;      // double
        var greeting = "Hi!";  // String
        System.out.println(count + " " + price + " " + greeting);
    }
}

var does not change how Java works; it only reduces the amount you have to write. The compiled bytecode is identical to code with explicit types.

var in complex type scenarios

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        var words = List.of("apple", "banana", "cherry", "date", "elderberry");

        // Long type with stream: var reduces noise
        var grouped = words.stream()
            .collect(Collectors.groupingBy(String::length));
        // grouped is Map<Integer, List<String>> - inferred

        grouped.forEach((len, ws) ->
            System.out.println("Length " + len + ": " + ws));

        // var in traditional for loop
        for (var entry : grouped.entrySet()) {
            // entry is Map.Entry<Integer, List<String>>
            System.out.println(entry.getKey() + " -> " + entry.getValue().size() + " words");
        }
    }
}

var is most valuable when the type is complex but clear from context, like Map.Entry<Integer, List<String>>. Writing the full type in a loop header adds noise without clarity.

When NOT to use var

import java.util.*;

public class Main {
    interface Shape { double area(); }
    static class Circle implements Shape {
        double radius;
        Circle(double r) { radius = r; }
        public double area() { return Math.PI * radius * radius; }
        double getRadius() { return radius; } // not on Shape
    }

    public static void main(String[] args) {
        // BAD: not clear what type x is
        var x = compute(); // what does compute() return?

        // GOOD: type is obvious from right-hand side
        var circle = new Circle(5.0);
        System.out.println("Area: " + circle.area());
        System.out.println("Radius: " + circle.getRadius()); // circle is Circle, not Shape

        // If you want Shape, declare it:
        Shape shape = new Circle(3.0);
        // shape.getRadius(); // compile error: getRadius not on Shape

        System.out.println("Answer: " + x);
    }

    static int compute() { return 42; }
}

With var, the inferred type is the most specific type on the right-hand side (Circle, not Shape). This can give you access to methods not in the interface, which can be a benefit or a subtle trap.

Common Mistakes

  • Using var when the type is not obvious from the right-hand side. var result = process(data); does not tell the reader what type result is
  • Trying to use var without an initializer: var x; is a compile error. You must provide an initial value so the compiler can infer the type
  • Confusing var with JavaScript/TypeScript var or dynamic typing. Java var is 100% statically typed; the type is fixed at compile time and cannot change

Note

A simple rule for var: if replacing var with the inferred type makes the code more readable, write the type. If the right-hand side already makes the type obvious (new SomeClass(), literal values), var is a good choice.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

Where can the var keyword be used?