Java Records vs. Lombok
In this article, we will be assessing whether Java Records could replace the Lombok library entirely. Here's what we think.
Join the DZone community and get the full member experience.
Join For FreeJava for a lot of time has been accused and mocked for its verbosity. Even the most passionate Java developers have to admit that it felt ridiculous to declare a bean class with two attributes. If you follow the right recommendations, you end up adding not only getters and setters, but also the implementations of toString hashcode and equals methods. The final result is a chunk of boilerplate that invites you to start learning another language.
import java.util.Objects;
public class Car {
private String brand;
private String model;
private int year;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", model='" + model + '\'' +
", year=" + year +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return year == car.year && Objects.equals(brand, car.brand) && Objects.equals(model, car.model);
}
@Override
public int hashCode() {
return Objects.hash(brand, model, year);
}
}
Gratefully, libraries like Lombok came to the rescue and reduced the pain in the eyes of a coder. With the new Java Records feature, one may wonder if it’s OK now to retire Lombok library. Let’s do a short analysis.
What is Lombok?
It’s a Java Library highly integrated with development environments that improve (spice) the code via annotations. It’s highly acceptable and used in the Java community.
Using Lombok our Car class looks like this:
import lombok.Data;
@Data
public class Car {
private String brand;
private String model;
private int year;
}
The code is much cleaner and pleasant to the eyes, without losing any functionality from our previous version.
What Is a Java Record?
A Java record can be shortly described as the implementation of the Value Object pattern. It’s a Java class where all its instances are immutable. Hence, all class attributes need to be passed during object creation. It was introduced in Java 14 and it is almost sure that it will continue evolving to improve class design.
The Car class as a record looks like this:
public record Car(String brand, String model, int year) {
}
It's also a huge improvement from our first version. But let's analyze a few aspects of Lombok and compare them to records to assess if we're ready to give permanent vacations to our spicy friend.
Immutability
Records are by default immutable. That means that all class attributes are declared implicitly as final. We can say they are like ValueObjects. They don’t have setter methods and all their values need to be passed in the constructor. Lombok can do the same using the @Value
annotation, but can also preserve the mutability using just the @Data
annotation.
import lombok.Value;
@Value
public class Car {
private String brand;
private String model;
private int year;
}
Bean Convention
Records are not meant to be compliant with bean conventions. The accessor methods are not named with getX and the class does not provide setters nor a no-args constructor. For Lombok on the other hand, simply using the @Data
annotation can convert a class into a Java Bean.
Builders
The builder pattern is a great design pattern to improve our object creation syntax. Lombok provides a convenient annotation that implements for us all the boilerplate code of this pattern. Java Records do not intend to provide this implementation (for now).
import lombok.Builder;
@Builder
public class Car {
private String brand;
private String model;
private int year;
public static void main(String[] args) {
Car myCamaro = Car.builder()
.brand("Chevrolet")
.model("Camaro")
.year(2022)
.build();
}
}
Big Classes
Records look good with a few fields. But add 10 fields to them and you will end up with a monster of a constructor with all the intrinsic problems of big constructors.
public record DetailedCar(
String brand, String model, int year,
String engineCode, String engineType, String requiredFuel,
String fuelSystem, String maxHorsePower, String maxTorque,
float fuelCapacity) {
}
DetailedCar camaroDetailed = new DetailedCar(
"Chevrolet", "Camaro", 2022, "LTG", "Turbocharged",
"Gas I4", "Direct Injection", "275 @ 560", "295 @ 3000-4500",
19.0f);
Using Lombok we could decide to leave the class as a bean having to option to use setters to set the state of the object or use a builder to have a cleaner way to construct the instance. The only caveat is that we could leave the instance in an incomplete state by not forcing all attributes to be set. In the case of the Builder annotation, we can mark all the class attributes as @nonNull
making them all required when building it. But this would throw an error at runtime, versus forcing a compilation error with records.
import lombok.Builder;
import lombok.NonNull;
@Builder
public class DetailedCar {
@NonNull
private String brand;
@NonNull
private String model;
@NonNull
private int year;
@NonNull
private String engineCode;
@NonNull
private String engineType;
@NonNull
private String requiredFuel;
@NonNull
private String fuelSystem;
@NonNull
private String maxHorsePower;
@NonNull
private String maxTorque;
@NonNull
private float fuelCapacity;
public static void main(String[] args) {
DetailedCar camaroIncomplete = DetailedCar.builder()
.brand("Chevrolet")
.model("Camaro")
.year(2022)
.build();
}
}
Output:
Exception in thread "main" java.lang.NullPointerException: engineCode is marked non-null but is null
Inheritance
Java record classes, as of this moment, do not support inheritance. You cannot make a record class extend another record class. This could be a limitation to model designing. Although, we all had learned that we should favor composition over inheritance.
Lombok annotations can be parametrized to consider parent class attributes in toString/hashcode/equals methods.
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Car extends MotorVehicle {
private String brand;
private String model;
private int year;
}
Conclusion
Java records are a great new feature of the language, moving in the right direction of cleaner code. We should start using them whenever there is an opportunity. But given the great versatility of Lombok library, and the slow (but surely) pace of Java language changes, it looks like it's too early to remove the Lombok dependency from our projects.
Opinions expressed by DZone contributors are their own.
Comments