96

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?

Vitalii
  • 10,091
  • 18
  • 83
  • 151

9 Answers9

77

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.

Michael A. Schaffrath
  • 1,992
  • 1
  • 14
  • 23
  • 4
    Caution! Currently this `@Builder.Default` is broken from v1.16.16 = It will initialise your fields to `null` regardless of the specified default initialised value set at the field level... See [Issue here](https://github.com/rzwitserloot/lombok/issues/1347) – DaddyMoe Feb 28 '18 at 11:49
  • The solution is not perfect as you have two initialize the field twice. Once on field declaration, and the second time in a constructor. It makes the code error-prone. I used to use this one https://stackoverflow.com/a/50392165/2443502 – Marcin Kłopotek May 17 '18 at 13:05
  • 1
    To make the code less error-prone, I've added a JUnit test which verify that the no-arg constructor is well written : `assertThat(new UserInfo().equals(UserInfo.builder().build()).describedAs("some properties marked with @Builder.Default are not properly set in the no-arg constructor").isTrue();` – Julien Kronegg Dec 06 '18 at 09:37
  • After the fix you can use a generated constructor if you have one to make your explicit constructor work: `@NoArgsConstructor @Builder class UserInfo { @Builder.Default boolean isEmailConfirmed = true; UserInfo(String custom) { this(); restOfConstructor(); } }` – mjaggard Feb 08 '23 at 17:32
54

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:

  • the field isEmailConfirmed is initialized only in one place making the code less error-prone and easier to maintain later
  • the UserInfo class will be initialized the same way either you use a builder or a no-args constructor

In 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.

Marcin Kłopotek
  • 5,271
  • 4
  • 31
  • 51
  • 2
    Since Lombok has many quirks that when you are not aware of may bring a lot of headaches than benefits you can also try either Immutables (https://immutables.github.io) or Autovalue (https://github.com/google/auto/blob/master/value/userguide/builders.md). – Marcin Kłopotek May 17 '18 at 13:21
  • 3
    All other tools have a single big disadvantage: They generate a new class. They have surely fewer problems as they're just annotation processors using a well-known (and ugly) API. Lombok has to work differently and it indeed has a few bugs. However, I'm using it since its beginning and I'm pretty happy with it. – maaartinus Nov 05 '18 at 17:32
  • The annotation isn't totally broken, but it has issues with non-primitive objects. The defaults are working for me with `Boolean` as of version `1.16.22` – Simon Tower Jan 10 '19 at 21:11
  • 1
    For anyone who wants to know how to do this when you have `@Singular` annotations, check out this comment: https://github.com/rzwitserloot/lombok/issues/1347#issuecomment-455429294 TLDR: Move your `@Singular` annotations into the newly created private constructor. – kibowki Jan 18 '19 at 05:12
  • 1
    It does not appear to be broken any longer with the newest version of Lombok. – David Conrad Mar 10 '20 at 17:48
  • `@Builder.Default` [is still broken](https://github.com/projectlombok/lombok/issues/2340). – Marcin Kłopotek Jul 06 '22 at 13:01
8

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;
    }
}
Sahil Chhabra
  • 10,621
  • 4
  • 63
  • 62
  • 11
    It is discouraged to have a logic in property based getters. Imagine the situation you want to add an additional method in the class, the method depends on the field (isEmailConfirmed). It can be confused for the implementor to use a field directly or call getter instead. The values can differ which may lead to bugs. – Marcin Kłopotek Oct 18 '18 at 10:58
  • @MarcinKłopotek Your point is good, but I feel that can be handle by using Deprecated annotation on that private field to restrict it using directly. Also, if I am the implementor of that class and i know i need an default value and i have written a code for it then obviously i should be knowing that i should not use that field directly for using it on some other logic method in that class. – Sahil Chhabra Oct 19 '18 at 02:49
  • 2
    @SahilChhabra Ambiguity is bad. Deprecated has a special purpose unless you want to make users of your code further confused. Lastly *assuming* that its "your class" and "you should be knowing " this field is not to be used etc. etc has another serious issue.. stick to the consistency and your code will show care to both future developers and its end users! – prash Jul 26 '19 at 14:46
  • @prash You can’t follow all the best practices everytime. Sometimes you have to deviate when using a buggy third party code. You can choose to not use the third party code at all or choose a work around by making sure that future developers are informed about the usage(here Deprecated can do that job well). It’s just a choice and opinion. I am happy that you have an opinion of your own. Also, I recommend you to read about Deprecated usage again - https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/deprecation/deprecation.html – Sahil Chhabra Jul 26 '19 at 18:02
  • I believe @Marcin Kłopotek answer is better since initialization concerns should be handled once at the constructor and not meant for overriding at getters (glad it has got higher numbers of up-votes). I am not sure if you have worked on EJBs where this type of code would have caused other issues. Same goes for multi-threading scenarios. Best practices is just not my opinion - it is industry wide standards because developers should try and minimize ambiguity. Yes Deprecated is still a way to go and appreciate that you have shared the link at least some marker is better than nothing! – prash Jul 29 '19 at 02:57
  • 2
    @prash I agree his answer is better. I just gave another way of doing it. I agree it has issues like you mentioned, but that depends on the use-case. Also, I never said anything against Best Practices. I just said sometimes you can't follow every best practice (again depending on the use-case). – Sahil Chhabra Jul 29 '19 at 06:19
  • Isn't this what `@Getter(lazy=true)` exists for? – Egor Hans Oct 01 '21 at 06:27
5

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");
LordOfThePigs
  • 11,050
  • 7
  • 45
  • 69
3

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();
laurent
  • 674
  • 9
  • 14
  • I use the same approach but instead of using: `UserInfo ui = new UserInfo().toBuilder().build();` I prefer using: `UserInfo ui = new UserInfo();` same thing with less code. – Ali Ertugrul Jul 21 '20 at 07:10
2

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.

0

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

gavenkoa
  • 45,285
  • 19
  • 251
  • 303
0

Initialize the properties in the No-Arg Constructor

converted
private boolean isEmailConfirmed = true;

to

public class UserInfo {

    public UserInfo() {
        this.isEmailConfirmed = true;
    }

}
ifelse.codes
  • 2,289
  • 23
  • 21
0

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;
    }
}
George L
  • 251
  • 2
  • 5