Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Understanding `var`: Type Inference and Restrictions

Recommended resources

Welcome to your fourth lesson. In our last session, we focused on how the Java compiler handles numeric types in expressions, specifically through promotion, casting, and overflow rules. We established that the compiler always determines a precise type for the result of any expression, such as int for the sum of two shorts.

Today, we'll see how that principle directly enables a modern Java feature: local variable type inference using var. This feature allows the compiler to deduce a variable's type from its initializer, reducing boilerplate code. For the OCP exam, it's not enough to know how to use var; it's far more important to know its limitations. This lesson is squarely focused on mastering the rules for applying var and, critically, identifying the specific contexts where its use is illegal.

1. Local Variable Type Inference: What var Is and Isn't

Introduced in Java 10, local variable type inference (LVTI) allows you to declare local variables without explicitly stating their type, using the var identifier instead. The compiler infers the type from the expression on the right-hand side of the declaration (the initializer).

Consider this standard declaration:

String message = "Hello, OCP 17!";

With var, it becomes:

var message = "Hello, OCP 17!";

The compiler sees that "Hello, OCP 17!" is a String literal, so it infers the type of the message variable to be String.

It's crucial to understand two points:

  1. This is a compile-time feature. The generated bytecode is identical to what it would be if you had declared the type explicitly. There is no runtime performance cost.
  2. Java remains a statically typed language. The type of the variable is determined once at compile time and cannot be changed later. var is purely a convenience for the developer, not a move towards dynamic typing like in JavaScript.

Var with Style: Local Variable Type Inference in Java 10 by Stuart Marks

To start, let's watch a brief, authoritative explanation from Stuart Marks, who was part of the team that designed this feature for Java. This clip from his Devoxx talk clarifies the core concept.

Please watch the segment from the beginning. Pay close attention to his emphasis on Java remaining statically typed and the two declarations being essentially equivalent at compile time.

The primary benefit of var is reducing verbosity, especially with complex generic types, which you frequently encounter in backend development.

What was once:

Map<String, List<Integer>> userScoresByDepartment = new HashMap<>();

Can now be written as:

var userScoresByDepartment = new HashMap<String, List<Integer>>();

This improves readability by allowing the developer to focus on the variable's name and purpose, rather than the ceremony of its type declaration.


2. The Golden Rules: Where var is Illegal

For the OCP exam, questions about var are most likely to test your knowledge of its limitations. A variable can be declared with var if and only if it is a local variable with an immediate initializer. Any deviation from this rule results in a compile-time error.

Let's break down the illegal contexts. The Baeldung guide offers a great summary of these rules.

Guide to var in Java

This article provides a concise list of illegal use cases for var. We'll use it as a reference as we go through each restriction.

Please read the section titled Illegal Use of var. This section covers most of the restrictions we'll discuss.

Based on that reading and the core rules, here are the situations where var is forbidden:

  1. Without an Initializer: The compiler infers the type from the initializer. Without one, inference is impossible.

    // COMPILE ERROR: cannot use 'var' on variable without initializer
    var score; 
    score = 100;
    
  2. Initialized to null: The null literal has a special "null type," but it can be assigned to a reference of any object type. The compiler refuses to guess which type you mean (e.g., String, Object, ArrayList).

    // COMPILE ERROR: variable initializer is 'null'
    var name = null;
    
  3. Class Fields (Instance or Static Variables): var is for local variables only. Fields are part of a class's API contract, and their types must be explicitly declared to maintain stability and prevent accidental API changes.

    public class Player {
        // COMPILE ERROR: 'var' is not allowed here
        private var score = 0; 
    }
    
  4. Method Parameters and Return Types: Like fields, method signatures are part of a public contract. Their types must be explicit.

    // COMPILE ERROR: 'var' is not allowed as a parameter type
    public int process(var input) { /* ... */ }
    
    // COMPILE ERROR: 'var' is not a valid return type
    public var createData() { /* ... */ }
    
  5. With Lambda Expressions or Method References: A lambda expression does not have an intrinsic type; it requires a "target type" (a specific functional interface) from its context. var needs to infer its type from the lambda, while the lambda needs its type from var—a circular dependency that the compiler cannot resolve.

    // COMPILE ERROR: lambda expression needs an explicit target-type
    var predicate = (String s) -> s.isEmpty();
    
  6. With Array Initializers: The shorthand array initializer syntax {1, 2, 3} is only legal when the type of the array is already explicitly known in the declaration.

    // COMPILE ERROR: array initializer needs an explicit target-type
    var numbers = {1, 2, 3}; 
    
    // This is the correct way
    int[] numbers = {1, 2, 3};
    

The following image provides a concise visual summary of several of these legal and illegal uses directly in an IDE context.

This screenshot demonstrates valid uses of `var` for an integer, a final String, and an ArrayList. It also highlights compile errors for invalid uses, such as initializing with `null` or using `var` for a method parameter.

This next video reinforces these restrictions with clear, concise examples.

How to use var keyword in java - Brain Bytes

This video from Java Brains effectively walks through the most common constraints on using var.

Please watch the following segments, which directly map to the rules we've just discussed: The first constraint: var requires an immediate initializer on the same line. Local scope only: var cannot be used for class fields or in method signatures. Lambda expressions: Explains why var cannot be used with lambdas.


3. Exam Nuances and "Gotchas"

Beyond the hard rules of what is legal, the OCP exam may test subtle behaviors of type inference.

var and the Diamond Operator (<>)

This is a classic source of confusion. The diamond operator (<>), introduced in Java 7, also performs type inference. When var and <> are used together, you must be careful.

  • List<String> names = new ArrayList<>(); — Here, <> infers String from the target type on the left, List<String>.
  • var names = new ArrayList<String>(); — Here, var infers ArrayList<String> from the explicit type on the right.

But what happens here?
var names = new ArrayList<>();

In this case, var needs to infer its type from the right side. But the right side, new ArrayList<>(), also needs type information. Since there is no target type on the left, the diamond operator has no information to work with and defaults to Object.

Therefore, var names = new ArrayList<>() declares names as an ArrayList<Object>. This is almost certainly not what you intended and can lead to runtime errors (ClassCastException) if you later try to retrieve elements assuming they are a more specific type.

Var with Style: Local Variable Type Inference in Java 10 by Stuart Marks

The designer of the feature, Stuart Marks, provides the definitive explanation for this interaction in his talk.

Watch the segment from this timestamp. He methodically breaks down how the compiler analyzes the line var v4 = new ArrayList<>() and why it results in ArrayList<Object>.

var and Coding to Interfaces

A common and recommended practice in Java is to "code to an interface," like so:

List<String> list = new ArrayList<>();

Here, the variable list has the interface type List, decoupling the code from the specific implementation ArrayList.

If you use var, the compiler infers the concrete type from the right-hand side:

var list = new ArrayList<String>(); // Type of 'list' is ArrayList<String>

The type of the list variable is now ArrayList<String>, not List<String>. This is perfectly valid code, but you lose the abstraction of coding to the interface in that variable's declaration. For the OCP exam, you must be able to state the exact inferred type.


Conclusion

In this lesson, we've explored var as a tool for local variable type inference, focusing on the strict rules that govern its use—knowledge that is essential for the OCP exam.

Key Takeaways:

  • var is for local variables with initializers only. It's a compile-time feature that does not change Java's static typing.
  • The type is inferred from the initializer expression on the right-hand side.
  • Illegal uses of var: You cannot use var for fields, method parameters, return types, uninitialized variables, variables initialized to null, or for lambda/array initializers.
  • var with the diamond operator (<>) can be tricky. If the diamond has no context, it defaults to Object, so var list = new ArrayList<>() creates an ArrayList<Object>.
  • var always infers the concrete type from the initializer, which is a consideration when you would typically code to an interface.

Understanding these rules allows you to predict compile-time errors and determine the precise type of a variable, skills that are directly tested on the exam.

In our next lesson, we will move on to another modern language feature related to String manipulation: text blocks. We will analyze their behavior with respect to whitespace, line terminators, and special characters.