1. Introduction

Project Lombok helps to reduce Java’s verbosity for repetitive tasks in our source code. In this tutorial, we’ll explain how to infer types by declaring local val and var variables in Lombok.

2. Declaring val and var Variables in Lombok

Lombok provides intelligent capabilities to avoid boilerplate code. For instance, it hides getters and setters from domain model objects. Builder annotation is another interesting feature that helps to implement the Builder pattern properly.

In the following sections, we’ll focus on the Lombok feature to define local variables without specifying the type. We’ll use Lombok val and var types to declare the variables and avoid extra lines in our source code.

val was introduced in version 0.10. When using val, Lombok declares the variable as final and automatically infers the type after initializing it. Thus, the initializing expression is mandatory.

var was introduced in version 1.16.20. As with val, it also infers the type from the initializing expression with the big difference that the variable is not declared as final. Therefore, further assignments are allowed, but they should comply with the type specified when declaring the variable.

3. Implementing val and var Examples in Lombok

3.1. Dependencies

To implement the examples, we’ll simply add the Lombok dependency to our pom.xml:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

We can check for the most recent available version here.

3.2. val Variable Declaration

First, we’ll import the val type from Lombok:

import lombok.val;

Second, we’ll declare different local variables using val. For instance, we can start with a simple String:

public Class name() {
    val name = "name";
    System.out.println("Name: " + name);
    return name.getClass();
}

Lombok automatically generates the following vanilla Java:

final java.lang.String name = "name";

Then, let’s create an Integer:

public Class age() {
    val age = Integer.valueOf(30);
    System.out.println("Age: " + age);
    return age.getClass();
}

As we can see, Lombok generates the proper type:

final java.lang.Integer age = Integer.valueOf(30);

We can also declare a List:

public Class listOf() {
    val agenda = new ArrayList<String>();
    agenda.add("Day 1");
    System.out.println("Agenda: " + agenda);
    return agenda.getClass();
}

Lombok infers not only the List but also the type inside it:

final java.util.ArrayList<java.lang.String> agenda = new ArrayList<String>();

Now, let’s create a Map:

public Class mapOf() {
    val books = new HashMap<Integer, String>();
    books.put(1, "Book 1");
    books.put(2, "Book 2");
    System.out.println("Books:");
    for (val entry : books.entrySet()) {
        System.out.printf("- %d. %s\n", entry.getKey(), entry.getValue());
    }
    return books.getClass();
}

Again, the proper types are inferred:

final java.util.HashMap<java.lang.Integer, java.lang.String> books = new HashMap<Integer, String>();
// ...
for (final java.util.Map.Entry<java.lang.Integer, java.lang.String> entry : books.entrySet()) {
   // ...
}

We can see Lombok declares the proper types as final. So, if we try to modify the name, the build will fail due to the final nature of val:

name = "newName";

[12,9] cannot assign a value to final variable name

Next, we’ll run some tests to verify Lombok generates the proper types:

ValExample val = new ValExample();
assertThat(val.name()).isEqualTo(String.class);
assertThat(val.age()).isEqualTo(Integer.class);
assertThat(val.listOf()).isEqualTo(ArrayList.class);
assertThat(val.mapOf()).isEqualTo(HashMap.class);

Finally, we can see in the console output the objects with specific types:

Name: name
Age: 30
Agenda: [Day 1]
Books:
- 1. Book 1
- 2. Book 2

3.3. var Variable Declaration

var declaration is pretty similar to val with the particularity that the variable is not final:

import lombok.var;

var name = "name";
name = "newName";

var age = Integer.valueOf(30);
age = 35;

var agenda = new ArrayList<String>();
agenda.add("Day 1");
agenda = new ArrayList<String>(Arrays.asList("Day 2"));

var books = new HashMap<Integer, String>();
books.put(1, "Book 1");
books.put(2, "Book 2");
books = new HashMap<Integer, String>();
books.put(3, "Book 3");
books.put(4, "Book 4");

Let’s have a look at the vanilla Java generated:

var name = "name";

var age = Integer.valueOf(30);

var agenda = new ArrayList<String>();

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

This is because Java 10 supports var declaration to infer types of local variables using the initializer expression. However, we’ll need to take into account some constraints when using it.

As the declared variable is not final, we can do further assignments. Nevertheless, the objects must fit the appropriate inferred type from the initializer expression.

If we try to assign a different type, we’ll get an error during compilation:

books = new ArrayList<String>();

[37,17] incompatible types: java.util.ArrayList<java.lang.String> cannot be converted to java.util.HashMap<java.lang.Integer,java.lang.String>

Let’s change the tests slightly and check the new assignments:

VarExample varExample = new VarExample();
assertThat(varExample.name()).isEqualTo("newName");
assertThat(varExample.age()).isEqualTo(35);
assertThat("Day 2").isIn(varExample.listOf());
assertThat(varExample.mapOf()).containsValue("Book 3");

And finally, the console output is also different from the previous section:

Name: newName
Age: 35
Agenda: [Day 2]
Books:
- 3. Book 3
- 4. Book 4

4. Compound Types

There are cases in which we’ll need to use compound types as the initializer expression:

val compound = isArray ? new ArrayList<String>() : new HashSet<String>();

In the above snippet, the assignment depends on the boolean value, and the most common superclass is inferred.

Lombok assigns AbstractCollection as the type as the vanilla code shows:

final java.util.AbstractCollection<java.lang.String> compound = isArray ? new ArrayList<String>() : new HashSet<String>();

In ambiguous cases, such as with null values, the class Object is inferred.

5. Configuration Keys

Lombok allows configuring the features in one file across the entire project. Thus, it is possible to include directives and settings for the project located in one place.

Sometimes, as part of enforcing development standards in our project, we may want to restrict the use of Lombok’s var and val. And, if someone does use them inadvertently, we may want to generate a warning during compilation.

For those cases, we can flag any usage of var or val as a warning or error by including the following in the lombok.config file:

lombok.var.flagUsage = error
lombok.val.flagUsage = warning

We’ll receive an error about the illegal use of var across the project:

[12,13] Use of var is flagged according to lombok configuration.

In the same way, we’ll receive a warning message about the use of val:

ValExample.java:18: warning: Use of val is flagged according to lombok configuration.
val age = Integer.valueOf(30);

6. Conclusion

In this article, we showed how to use Lombok to define local variables without specifying the type. Furthermore, we learned the intricacies of declaring val and var variables.

We also demonstrated how the generic declaration of local variables works with compound types.

As always, the code is available over on GitHub.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.