Setup for failure: fusing java knowledge with assumption from tool
Lombok is one of my favourite tool to give Java code a refreshing look. The more I use it the more I appreciate author’s design intention. Without it, event with IDE tricks to fold code from getter, setter methods java classes still look ugly in all my code bases. However, fusing java knowledge with lombok generated code catches me off guard with @Builder annotation.
Naive java knowledge
Java from day one, if we declare field with initialized value the field will not be null. Something like this:
class SomeDataClass {
private int metricData = 0;
private Map<String, String> configurations = new HashMap<>();
private String normalField;
}
If we found a class look similar to above sample, we can know for sure that metricData field will not null
,
configurations field also will not be null
when we have new SomeDataClass()
because we already initialize them. This
is basic Java 101.
Now we want to save a few bytes on the class and make it more handy with lombok by using:
@Builder
@Data
class SomeDataClass {
private int metricData = 0;
private Map<String, String> configurations = new HashMap<>();
private String normalField;
}
The failure
then we use the shiny new Builder class by calling:
var data=SomeDataClass.Builder()
.setNormalField(someValue)
.build();
This is because I think that @Builder does use new
keyword for having a new instance of SomeDataClass
but actually,
The generated Builder class depends on setter method from builder instance before setting back to new instance we need.
In turn, metricData
and configurations
fields are null
.
That’s silly bug introduced by not reading the document form Lombok page. When I see that happen, I just wow my silly brain…
@Builder.Default
If a certain field/parameter is never set during a build session, then it always gets 0 / null / false. If you've put @Builder on a class (and not a method or constructor) you can instead specify the default directly on the field, and annotate the field with @Builder.Default:
@Builder.Default private final long created = System.currentTimeMillis();
The fix is easy, having another annotation at field to notify Lombok that the field also should be initialized for builder object. We now can fix above example as:
The fix
@Builder
@Data
class SomeDataClass {
@Builder.Default
private int metricData = 0;
@Builder.Default
private Map<String, String> configurations = new HashMap<>();
private String normalField;
}