Skip to content

Silly bug by not reading documentation @Builder annotation from lombok library

Posted on:November 29, 2023 at 03:23 PM

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;
}