When writing Java, I’m a big fan of Lombok, a library that helps reduce boilerplate code through code-generation. I’ve written about it in the past, if you’re not familiar with it, checkout my previous posts here and here.
Recently I’ve been confronted with the problem of providing defaults for generated Builder-classes generated by the @Builder annotation. I was expecting that providing default values in the class that @Builder is defined on should be respected when creating instances through a builder, but it turned out that this doesn’t work.
@Builder
public class Person {
private String firstname = "John";
private String lastname = "Doe";
}
Instead, what you have to do is provide a minimal Builder implementation containing the default values and let Lombok fill in the rest:
@Builder
public class Person {
private String firstname;
private String lastname;
private String middleName;
public static class PersonBuilder {
private String firstname = "John";
private String lastname = "Doe";
}
}
Lombok will add any generated code to the existing class and those default values will be respected in the construction process. You can find additional background on this here.
Update:
Lombok v1.16.16 adds a new feature that makes default builder values a bit easier to work with: @Builder.Default. You use it on the field that has a default value defined an Lombok will then pick up the value during object creation:
@Builder
public class Person {
@Builder.Default private String firstname = "John";
@Builder.Default private String lastname = "Doe";
private String middleName;
}
Great post! Thank you!
you exposed the little secret about Lombok.
very helpful. thanks
That’s great, Thanks.
It works, but if we use this and use generic type for the class at same time with @Builder, the builder will not work due to accessibility of static class 🙁
Maybe you have already solution for this issue ?
Regards
I’ve given it a quick shot, I have trouble with the correct resolution of the type parameter. Is that what you mean? If you like, share a gist and I’ll have another look.
You’ve just made my day 🙂
On a side note: if you want your collections to be by default initialized with a size of zero vs defaulting to null, annotate it with @Singular when using @Builder.
Hi Reinhard,
nice blog but you can to this better by set the toBuilder to true and the you have a Builder that takes the values of the current bean. In your example:
@Builder(toBuilder=true)
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String firstname = “John”;
private String lastname = “Doe”;
}
…
Person person = new Person().toBuilder().build();
toBuilder()
Is a very handy feature, but this way you have to create an extra object before you can use a builder. I like my approach a bit better.Great post, but I’d recommend you to add annotation parameter builderClassName so default values can survive class refactoring. If you rename Person class to e.g. MainPerson default values will be ignored (default builder name will be MainPersonBuilder) till you rename inner class, too.
Example code which survives class refactoring:
@Builder(builderClassName = “Builder”)
public class Person {
private String firstname;
private String lastname;
private String middleName;
public static class Builder {
private String firstname = “John”;
private String lastname = “Doe”;
}
}
Good thinking, thanks for mentioning that!
If you make the fields final, default values will work without the additional partial builder implementation.
In many cases, striving for immutability, you would like to make them final anyway.
Found another problem, @Builder.Default truly solve the default values problem, but the @NoArgsConstructor doesn’t take the default values anymore.
If no @Builder.Default on field, @NoArgsConstructor would take the default values
when use new Person()
Interesting, you’re right. Something to be aware of for sure. I wonder if this is on purpose or if it should be reported…
@Builder.Default breaks other class constructors and consequently does not play well with Jackson or other packages that use reflection to call a class constructor.
What about using default with SuperBuilder?