Optional & Modern Java

The var Keyword

15 min Lesson 6 of 13

The var Keyword

Java 10 introduced var — a reserved type name that lets the compiler infer the type of a local variable from its initializer. Despite looking like a dynamically typed construct from JavaScript or Kotlin, var is strictly static: the type is resolved at compile time, it is fixed for the lifetime of the variable, and the bytecode is identical to writing the explicit type yourself.

Where var Is Allowed

var is legal in exactly these positions:

  • Local variable declarations inside methods, constructors, and initializer blocks — provided they have an initializer on the same line.
  • The loop variable in an enhanced for-loop (for (var item : list)).
  • The iteration variable in a traditional for-loop (for (var i = 0; i < n; i++)).
  • Try-with-resources variables (try (var reader = ...)).

Everywhere else — method parameters, return types, constructor parameters, fields, catch parameters, lambda parameters — var is not permitted.

var is not a keyword. Technically it is a reserved type name (JEP 286). That means you can still have a method or variable named var without a compile error — but you really should not.

Basic Inference in Action

The compiler replaces var with the static type of the right-hand side:

// These two declarations are byte-for-byte identical after compilation String explicit = "hello"; var inferred = "hello"; // type inferred as String // Complex types become far less noisy with var Map<String, List<Integer>> explicit2 = new HashMap<>(); var inferred2 = new HashMap<String, List<Integer>>();

Notice that in the second example the type argument must appear on the right-hand side when using var, because the compiler has nothing else to infer from. var x = new HashMap<>() would infer HashMap<Object, Object> — probably not what you wanted.

var with Streams and Lambdas

One of the most practical uses is taming verbose intermediate types in stream pipelines and try-with-resources blocks:

import java.util.*; import java.util.stream.*; List<String> names = List.of("Alice", "Bob", "Carol"); // var keeps focus on the operation, not the type var grouped = names.stream() .collect(Collectors.groupingBy(String::length)); // grouped is Map<Integer, List<String>> — the IDE and compiler know this grouped.forEach((len, list) -> System.out.println(len + " -> " + list));
// var in try-with-resources is especially clean try (var lines = Files.lines(Path.of("data.txt"))) { lines.filter(l -> l.startsWith("#")).forEach(System.out::println); }

var in Enhanced For-Loops

The enhanced for-loop is perhaps the single place where var shines most consistently — it eliminates the type repetition when the collection type already makes the element type obvious:

List<Map.Entry<String, Integer>> entries = Map.of("a", 1, "b", 2).entrySet() .stream().toList(); // Without var — entry type is spelled out twice for (Map.Entry<String, Integer> entry : entries) { System.out.println(entry.getKey() + "=" + entry.getValue()); } // With var — same semantics, less noise for (var entry : entries) { System.out.println(entry.getKey() + "=" + entry.getValue()); }

Adding Annotations to var

You can annotate a var variable exactly like an explicit-type variable:

@SuppressWarnings("unchecked") var raw = getRawList(); // annotation applies to the inferred type

Readability — The Real Trade-Off

var is a readability tool, not a type-hiding trick. The key question is always: "Can a reader determine the type from context within the same few lines?"

Use var when the type is obvious or unimportant to the reader:

// GOOD: the type is self-evident from the right-hand side var message = "Order confirmed"; var count = items.size(); var formatter = DateTimeFormatter.ISO_LOCAL_DATE; var conn = dataSource.getConnection();

Avoid var when it hides information the reader needs:

// BAD: what does processOrder() return? You have to navigate to find out. var result = processOrder(id); // BETTER: explicit return type makes the contract clear Order result = processOrder(id); // BAD: the numeric literal type is ambiguous — is it int? long? var timeout = 30; // reader must check usage to understand intent // BETTER: be explicit when the primitive type matters long timeout = 30L;
The "70-character rule of thumb" — if the explicit type on the left would push the line past 70–80 characters and is already fully expressed on the right-hand side (constructor call, factory method, cast), var improves readability. If you are just calling an opaque method, prefer the explicit type.

What var Cannot Do

Because inference needs a definite type from the initializer, these are all compile errors:

var nothing; // Error: no initializer var nothing2 = null; // Error: cannot infer type from null alone var lambda = () -> "hi"; // Error: var cannot infer a functional interface var array = {1, 2, 3}; // Error: array initializer without explicit type
var does not change the type — it just omits writing it. You cannot assign an incompatible type to a var variable after declaration, and the compiler will reject it. This is nothing like JavaScript's var.

Interaction with Generics and Diamond

When the right-hand side uses the diamond operator and var is on the left, the compiler widens generic bounds to Object:

// Infers ArrayList<Object> — probably wrong var list = new ArrayList<>(); // Infers ArrayList<String> — what you want var list2 = new ArrayList<String>(); // Or let the explicit type guide the diamond on the right ArrayList<String> list3 = new ArrayList<>();

Summary

var reduces visual clutter for local variables when the type is already apparent from the right-hand side. It is especially useful with generics, stream pipelines, for-each loops, and try-with-resources. Always ask whether removing the explicit type makes the code clearer or just shorter — they are not the same thing. In the next lesson we meet text blocks, another modern Java feature aimed squarely at readability.