1. Overview
Project Lombok reduces boilerplate code in Java applications by providing annotations that automatically generate commonly used code.
In this tutorial, we’ll explore the differences between the three constructor annotations offered by this library.
2. Setup
To highlight these differences, let’s begin by adding lombok to our dependencies:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
Next, let’s create a class to serve as the basis for our demonstration:
public class Person {
private int age;
private final String race;
@NonNull
private String name;
private final String nickname = "unknown";
}
We deliberately sprinkled various non-access modifiers throughout our Person object, which each constructor annotation handles differently. We’ll use a copy of this class with a different name for each of the following sections.
3. @AllArgsConstructor
As the name suggests, the @AllArgsConstructor annotation generates a constructor initializing all object fields. Fields annotated with @NonNull undergo a null check in the resulting constructor.
Let’s add the annotation to our class:
@AllArgsConstructor
public class AllArgsPerson {
// ...
}
Next, let’s trigger a null check in the generated constructor:
@Test
void whenUsingAllArgsConstructor_thenCheckNotNullFields() {
assertThatThrownBy(() -> {
new AllArgsPerson(10, "Asian", null);
}).isInstanceOf(NullPointerException.class)
.hasMessageContaining("name is marked non-null but is null");
}
The @AllArgsConstructor provided us with a AllArgsPerson constructor containing all the necessary fields of the object.
4. @RequiredArgsConstructor
The @RequiredArgsConstructor generates a constructor that initializes only fields marked as final or @NonNull, provided they weren’t initialized upon declaration.
Let’s update our class with the @RequiredArgsConstructor:
@RequiredArgsConstructor
public class RequiredArgsPerson {
// ...
}
With our RequiredArgsPerson object, this results in a constructor with only two parameters:
@Test
void whenUsingRequiredArgsConstructor_thenInitializedFinalFieldsWillBeIgnored() {
RequiredArgsPerson person = new RequiredArgsPerson("Hispanic", "Isabela");
assertEquals("unknown", person.getNickname());
}
Since we initialized the nickname field, it won’t become part of the generated constructor arguments, despite being final. Instead, it’s treated like other non-final fields and those not marked as @NotNull.
Like @AllArgsConstructor, the @RequiredArgsConstructor annotation also conducts a null check for fields annotated with @NonNull, as demonstrated in our unit test:
@Test
void whenUsingRequiredArgsConstructor_thenCheckNotNullFields() {
assertThatThrownBy(() -> {
new RequiredArgsPerson("Hispanic", null);
}).isInstanceOf(NullPointerException.class)
.hasMessageContaining("name is marked non-null but is null");
}
5. @NoArgsConstructor
Normally, if we haven’t defined a constructor, Java provides a default one. Likewise, @NoArgsConstructor generates a no-argument constructor for a class, resembling the default constructor. We specify the force parameter flag to avoid compilation errors caused by uninitialized final fields:
@NoArgsConstructor(force = true)
public class NoArgsPerson {
// ...
}
Next, let’s check for defaults on an uninitialized field:
@Test
void whenUsingNoArgsConstructor_thenAddDefaultValuesToUnInitializedFinalFields() {
NoArgsPerson person = new NoArgsPerson();
assertNull(person.getRace());
assertEquals("unknown", person.getNickname());
}
Unlike the other fields, the nickname field didn’t receive a null default value because we initialized it upon declaration.
6. Using Multiple Annotations
In certain cases, differing requirements may lead to the use of multiple annotations. For example, if we prefer to offer a static factory method but still need a default constructor for compatibility with external frameworks such as JPA, we can use two annotations:
@RequiredArgsConstructor(staticName = "construct")
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class SpecialPerson {
// ...
}
Thereafter, let’s call our static constructor with sample values:
@Test
void whenUsingRequiredArgsConstructorWithStaticName_thenHideTheConstructor() {
SpecialPerson person = SpecialPerson.construct("value1", "value2");
assertNotNull(person);
}
In this scenario, attempting to instantiate the default constructor results in a compilation error.
7. Comparison Summary
Let’s summarize in a table what we’ve discussed:
Annotation | Generated constructor arguments | @NonNull field null check |
---|---|---|
@AllArgsConstructor | All object fields (except for static and initialized final fields) | Yes |
@RequiredArgsConstructor | Only final or @NonNull fields | Yes |
@NoArgsConstructor | None | No |
8. Conclusion
In this article, we explored the constructor annotations offered by Project Lombok. We learned that @AllArgsConstructor initializes all object fields, whereas @RequiredArgsConstructor initializes only final and @NotNull fields. Additionally, we discovered that @NoArgsConstructor generates a default-like constructor, and we discussed how these annotations can be used together.
As always, the source code for all the examples can be found over on GitHub.