Reputation: 11071
I have an object
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
private int id;
private String nick;
private boolean isEmailConfirmed = true;
}
And I initialize it in two ways
UserInfo ui = new UserInfo();
UserInfo ui2 = UserInfo.builder().build();
System.out.println("ui: " + ui.isEmailConfirmed());
System.out.println("ui2: " + ui2.isEmailConfirmed());
Here is output
ui: true
ui2: false
It seems that builder does not get a default value. I add @Builder.Default
annotation to my property and my object now looks like this
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
private int id;
private String nick;
@Builder.Default
private boolean isEmailConfirmed = true;
}
Here is console output
ui: false
ui2: true
How can I make them both be true
?
Upvotes: 108
Views: 203198
Reputation: 5931
Since the @Builder.Default
annotation is broken, I wouldn't use it at all. You can, however, use the following approach by moving the @Builder
annotation from class level to the custom constructor:
@Data
@NoArgsConstructor
public class UserInfo {
private int id;
private String nick;
private boolean isEmailConfirmed = true;
@Builder
@SuppressWarnings("unused")
private UserInfo(int id, String nick, Boolean isEmailConfirmed) {
this.id = id;
this.nick = nick;
this.isEmailConfirmed = Optional.ofNullable(isEmailConfirmed).orElse(this.isEmailConfirmed);
}
}
This way you ensure:
isEmailConfirmed
is initialized only in one place making the code less error-prone and easier to maintain laterUserInfo
class will be initialized the same way either you use a builder or a no-args constructorIn other words, the condition holds true
:
new UserInfo().equals(UserInfo.builder().build())
In that case, the object creation is consistent no matter how you create it. It is especially important when your class is used by a mapping framework or by JPA provider when you are not instantiating it manually by a builder but a no-args constructor is invoked behind your back to create the instance.
The approach described above is very similar but it has a major drawback. You have to initialize the field in two places which makes the code error-prone as you are required to keep the values consistent.
Upvotes: 57
Reputation: 2110
My guess is that it's not possible (without having delomboked the code). But why don't you just implement the constructor you need? Lombok is meant to make your life easier, and if something won't work with Lombok, just do it the old fashioned way.
@Data
@Builder
@AllArgsConstructor
public class UserInfo {
private int id;
private String nick;
@Builder.Default
private boolean isEmailConfirmed = true;
public UserInfo(){
isEmailConfirmed = true;
}
}
Console output:
ui: true
ui2: true
Update
As of 01/2021, this bug seems to be fixed in Lombok, at least for generated constructors. Note that there is still a similar issue when you mix Builder.Default
and explicit constructors.
Upvotes: 88
Reputation: 261
You can create a static Builder class with default values populated:
@Data
@Builder(builderClassName="Builder")
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
private int id;
private String nick;
private boolean isEmailConfirmed;
public static class Builder{
//Set defaults here
private boolean isEmailConfirmed = true;
}
}
Upvotes: 1
Reputation: 178
In version 1.18.2 both @NoArgsConstructor
and @Builder
work, but not completely.
Constructor with one or more fields will null all other default initialisations: new UserInfo("Some nick")
will cause isEmailConfirmed
to be false again.
My way to handle this is:
public UserInfo(String nick) {
this();
this.nick = nick;
}
This way all default fields will be initialised and we'll get expected constructor.
Upvotes: 2
Reputation: 2389
Initialize the properties in the No-Arg Constructor
converted
private boolean isEmailConfirmed = true;
to
public class UserInfo {
public UserInfo() {
this.isEmailConfirmed = true;
}
}
Upvotes: 1
Reputation: 11300
My experience is that @Builder
works best when it is the only means of instantiating a class, and therefore works best when paired with @Value
rather than @Data
.
For classes where all fields are mutable in any order anyway, and for which you want to keep the chained calls, consider replacing it with @Accessors(chain=true)
or @Accessors(fluent=true)
.
@Data
@Accessors(fluent=true)
public class UserInfo {
private int id;
private String nick;
private boolean isEmailConfirmed = true;
}
This allows you to construct your objects fluently in the code, and avoid un-necessary creation of Builder objects:
UserInfo ui = new UserInfo().id(25).nick("John");
Upvotes: 5
Reputation: 48743
Custom constructors and @Builder.Default
probably will never work together.
Framework authors want to avoid double initializations for @Builder
.
I reuse .builder()
by public static CLAZZ of(...)
methods:
@Builder
public class Connection {
private String user;
private String pass;
@Builder.Default
private long timeout = 10_000;
@Builder.Default
private String port = "8080";
public static Connection of(String user, String pass) {
return Connection.builder()
.user(user)
.pass(pass)
.build();
}
public static Connection of(String user, String pass, String port) {
return Connection.builder()
.user(user)
.pass(pass)
.port(port)
.build();
}
public static Connection of(String user, String pass, String port, long timeout) {
return Connection.builder()
.user(user)
.pass(pass)
.port(port)
.timeout(timeout)
.build();
}
}
Check corresponding discussion: https://github.com/rzwitserloot/lombok/issues/1347
Upvotes: 0
Reputation: 775
Here's my approach :
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class UserInfo {
private int id;
private String nick;
private boolean isEmailConfirmed = true;
}
And then
UserInfo ui = new UserInfo().toBuilder().build();
Upvotes: 3
Reputation: 11666
Another way is define your own getter method overriding the lombok getter:
@Data
@Builder
@AllArgsConstructor
public class UserInfo {
private int id;
private String nick;
private Boolean isEmailConfirmed;
public Boolean getIsEmailConfirmed(){
return Objects.isNull(isEmailConfirmed) ? true : isEmailConfirmed;
}
}
Upvotes: 10