JB Header

Java var Keyword: Local Variable Type Inference

Introduction Since Java 10, you can declare a local variable with var instead of writing its type, and let the compiler work the type out. This is called local variable type inference. This tutorial explains the var keyword in Java with examples. We will start with how var works, and why a var variable is still strongly typed. We will then see where var can be used, and the places where it cannot. Next we will look at some surprises in the types var infers. Lastly, we will go through guidelines for using var so that it makes code clearer, not harder to read.

(Note - This tutorial assumes you know how to declare variables in JavaClick to read tutorial: Variables in Java: Declaration, Initialization and Scope.) How var works With var, the compiler looks at the value you initialize the variable with, and gives the variable that value's type. These two declarations produce exactly the same variable -
An explicit type and var
ArrayList<String> names = new ArrayList<String>();
var names = new ArrayList<String>();          // inferred type: ArrayList<String>
var does not make Java dynamically typed. The type is decided once, at compile time, and never changes. Assigning a value of a different type later is a compile error, just as if you had written the type yourself -
A var variable keeps its inferred type
//VarIsStatic.java
public class VarIsStatic {
  public static void main(String[] args) {
    var count = 10;        // inferred as int
    count = "ten";
  }
}
 OUTPUT on compiling the above code
VarIsStatic.java:5: error: incompatible types: String cannot be converted to int
    count = "ten";
            ^
1 error
var only saves you from writing the type; the compiled code is identical. It is a reserved type name rather than a full keyword, so existing code with a variable or method called var still compiles.
Where var can be used var works for local variables which are initialized when they are declared - in methods and blocks, in for and enhanced for loops, and in try-with-resources statements.
Using var in local variables and loops
//VarDemo.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class VarDemo {
  public static void main(String[] args) {
    var message = "Hello";                    // String
    var count = 3;                            // int
    var price = 19.99;                        // double
    var names = new ArrayList<String>();      // ArrayList<String>
    names.add("Tom");
    names.add("Harry");

    var marks = new HashMap<String, List<Integer>>();
    marks.put("Tom", List.of(78, 91));
    marks.put("Harry", List.of(85, 64));

    for (var i = 0; i < count; i++) {         // var in a for loop
      System.out.print(message + " ");
    }
    System.out.println();

    for (var entry : marks.entrySet()) {      // Map.Entry<String, List<Integer>>
      System.out.println(entry.getKey() + " -> " + entry.getValue());
    }
    System.out.println("price is a " + ((Object) price).getClass().getSimpleName());
  }
}
 OUTPUT of the above code
Hello Hello Hello
Tom -> [78, 91]
Harry -> [85, 64]
price is a Double
Explanation of the code
  • The biggest win is with long generic types. Without var, the loop variable would be written Map.Entry<String, List<Integer>> entry.
  • var price = 19.99; inferred double, the type of the literal 19.99. The last line confirms it: the value, boxed as an object, is a Double. (A HashMap does not keep its entries in insertion order, so the names may print in any order.)
  • With var, the type is visible on the right-hand side - in new ArrayList<String>() - so writing it on the left would add nothing.
Where var cannot be used The compiler must be able to work out the type from the declaration alone, so var is not allowed -
  • without an initializer - var x;
  • with null as the initializer - var x = null; (null has no useful type)
  • with an array initializer alone - var nums = {1, 2, 3}; (but var nums = new int[] {1, 2, 3}; is fine)
  • for fields, method parameters and method return types
  • for several variables in one declaration - var a = 1, b = 2;
Local variables where var cannot infer a type
//VarErrors.java
public class VarErrors {
  public static void main(String[] args) {
    var noValue;
    var nothing = null;
    var nums = {1, 2, 3};
  }
}
 OUTPUT on compiling the above code
VarErrors.java:4: error: cannot infer type for local variable noValue
    var noValue;
        ^
  (cannot use 'var' on variable without initializer)
VarErrors.java:5: error: cannot infer type for local variable nothing
    var nothing = null;
        ^
  (variable initializer is 'null')
VarErrors.java:6: error: cannot infer type for local variable nums
    var nums = {1, 2, 3};
        ^
  (array initializer needs an explicit target-type)
3 errors
And for a field -
var is not allowed for fields
//VarField.java
public class VarField {
  var count = 10;
}
 OUTPUT on compiling the above code
VarField.java:3: error: 'var' is not allowed here
  var count = 10;
  ^
1 error
Note - Since Java 11, var can also be used for the parameters of lambda expressions, mainly so that annotations can be added to them - for example (@NonNull var s) -> s.length().
Surprises in the inferred type var infers the exact type of the initializer, which is not always the type you would have written -
Inferred types that may surprise you
//VarSurprises.java
import java.util.ArrayList;
import java.util.List;

public class VarSurprises {
  public static void main(String[] args) {
    var small = 10;             // int, not long or byte
    var big = 10L;              // long
    var ratio = 10f;            // float
    var initial = 'T';          // char, not String

    var list = new ArrayList<>();       // ArrayList<Object> - the diamond has nothing to infer from
    list.add("Tom");
    list.add(42);
    System.out.println("list holds anything: " + list);

    List<String> typed = new ArrayList<>();   // the declared type tells the diamond what to use
    typed.add("Tom");
    System.out.println(small + " " + big + " " + ratio + " " + initial + " " + typed);
  }
}
 OUTPUT of the above code
list holds anything: [Tom, 42]
10 10 10.0 T [Tom]
Explanation of the code
  • Numeric literals decide the type: 10 is an int, 10L a long and 10f a float. If you need a long or a byte, use the suffix or write the type.
  • 'T' in single quotes is a char, not a String.
  • var list = new ArrayList<>() infers ArrayList<Object>, which accepts anything and loses type safety. Do not combine var with the empty diamond <> - write the type argument, as in new ArrayList<String>().
Guidelines for using var
  • Use var when the type is obvious from the right-hand side - a new expression, a literal, or a well-named factory method such as LocalDate.now().
  • Write the type when it is not obvious. In var result = process(order); the reader cannot tell what result is without looking up process().
  • Choose good variable names. With var, the name carries more of the meaning - var customers tells more than var list.
  • Keep scopes small. var works best for variables used within a few lines of their declaration.
Summary In this tutorial we first saw how var lets the compiler infer the type of a local variable from its initializer, and that the variable is still statically typed. We then used var for local variables and loop variables, and saw the places where it is not allowed - without an initializer, with null or a bare array initializer, and for fields, parameters and return types. Next we looked at surprising inferred types, such as int for numeric literals and ArrayList<Object> with the empty diamond. Lastly, we went through guidelines for using var well. In the next tutorial, we start on Java's operators with arithmetic, relational and logical operatorsClick to read tutorial: Java Arithmetic, Relational and Logical Operators.