102

If I add @Builder to a class. The builder method is created.

Person.builder().name("john").surname("Smith").build();

I have a requirement where a particular field is required. In this case, the name field is required but the surname is not. Ideally, I would like to declare it like so.

Person.builder("john").surname("Smith").build()

I can't work out how to do this. I have tried adding the @Builder to a constructor but it didn't work.

@Builder
public Person(String name) {
    this.name = name;
}
mernst
  • 7,437
  • 30
  • 45
jax
  • 37,735
  • 57
  • 182
  • 278
  • Lombok opend issues on GitHub has one open issue for this https://github.com/rzwitserloot/lombok/issues/1043 – lennykey Feb 14 '18 at 10:59
  • It won't probably work with lombok, since you need access to the actual builder source code, but if you do have access then you can try a plugin I developed to solve exactly these kind of issues: https://github.com/banterly91/Java-Builder-Guided-Completion-Intellij-Plugin – dragosb Jan 26 '21 at 11:37

14 Answers14

110

You can do it easily with Lombok annotation configuration

import lombok.Builder;
import lombok.ToString;

@Builder(builderMethodName = "hiddenBuilder")
@ToString
public class Person {

    private String name;
    private String surname;

    public static PersonBuilder builder(String name) {
        return hiddenBuilder().name(name);
    }
}

And then use it like that

Person p = Person.builder("Name").surname("Surname").build();
System.out.println(p);

Of course @ToString is optional here.

Pawel
  • 3,558
  • 1
  • 25
  • 24
  • 3
    it would be better if you could explain a bit more. – Blip Jun 16 '15 at 12:19
  • @Blip it means the generator will create a method called `hiddenBuilder` instead of `builder`. The custom `builder` method then calls it with the required arguments (which can be anything you like). – jax Jun 16 '15 at 12:39
  • @Blip as jax explaind `builderMethodName` value is a name of method that will be automatically created. The request was to have `builder` method with `String name` parameter, so that's why different name of automatically generated builder method was needed. Also you should read the documentation of [lombok @Builder](https://projectlombok.org/features/Builder.html) to understand that lombok is also generating `PersonBuilder` class for us. – Pawel Jun 19 '15 at 14:48
  • 57
    what's not making sense to me with this answer is that hiddenBuilder() isn't hidden... – Kevin Day Mar 17 '16 at 20:15
  • @KevinDay it is hidden from an outside point of view. From outside you just call builder() and that function will call hiddenBuilder() – Akshat Agarwal Oct 13 '16 at 16:45
  • 5
    @AkshatAgarwal what makes the builder method hidden? I'm 99.99% certain the builderMethodName just changes the name of the method - it doesn't change the method to hidden. So I'm still not seeing any way to achieve the desired outcome of having required fields. – Kevin Day Nov 09 '16 at 01:08
  • @KevinDay you are right, it doesnt hide it. Sorry for the mis-information – Akshat Agarwal Nov 09 '16 at 16:37
  • Sorry builder() is still visible! – emeraldhieu Jan 03 '17 at 06:53
  • So in Lombok you can not achieve such an effect to create a builder with an argument? – Kamil Tomasz Jarmusik Aug 06 '18 at 07:36
  • 1
    adding to this, to make the hiddenBuilder hidden for real, you can add the following code to the class `private PersonBuilder hiddenBuilder() { return new PersonBuilder(); }` – Max Apr 28 '19 at 22:33
  • 15
    I would just tell Lombok to make the builder private: `@Builder(builderMethodName = "hiddenBuilder", access = AccessLevel.PRIVATE)` – Linus Oct 22 '19 at 20:19
  • 1
    Just adding to the @Linus comment that in order to set access you need at least Lombok v1.18.8 – labm0nkey Nov 12 '19 at 14:58
  • 7
    @Linus it seems like adding AccessLevel.PRIVATE makes all of the builder's methods private as well, and deems it quite useless. Am I mistaken? – Dean Gurvitz Jan 26 '20 at 12:16
  • @DeanGurvitz I just checked with version 1.18.10, all of the builder's methods remain public. – Linus Jan 28 '20 at 20:41
  • 2
    @DeanGurvitz Forgot to update the Lombok Plugin for IntelliJ Idea. You are right, all methods are private, so that approach doesn't work. – Linus Jan 28 '20 at 21:26
  • 2
    Starting with lombok v1.18.8 you can do `@Builder(builderMethodName = "")`, causing lombok to now create any builder method for you. Then you can create it yourself as shown in this answer. – Scott G Nov 24 '20 at 21:34
  • 2
    To make the builder private you can simply use : `@Builder(builderMethodName = "") `and instantiate it with his constructor inside the method : `public static PersonBuilder builder(String name) { return new PersonBuilder().name(name); }` – Philippe Escure Jan 27 '22 at 10:12
62

I would recommend against this approach, as you will struggle to apply it consistently on other objects. Instead, you can just mark fields with @lombok.NonNull annotation and Lombok will generate null checks for you in the constructor and setters, so that Builder.build() will fail, if those fields are not set.

Using builder pattern allows you to have very clear identification of which fields you're setting to which values. This is already lost for name field in your example, and it will further be lost for all other required fields, if you're building an object with multiple required fields. Consider the following example, can you tell which field is which just by reading code?

Person.builder("John", "Michael", 16, 1987) // which is name, which is surname? what is 16?
    .year(1982) // if this is year of birth, then what is 1987 above?
    .build()
Anton Koscejev
  • 4,603
  • 1
  • 21
  • 26
  • 63
    Runtime error vs Compile time error. Always favour a compile time error! – jax Mar 30 '16 at 22:08
  • 9
    @jax They don't check the same thing. Requiring a field to be set doesn't check for null value. Checking for null will be runtime error (in pure Java) regardless of whether you require the field or not. – Anton Koscejev Apr 01 '16 at 06:35
  • No, the builder method cannot be run until all required arguments are given thus satisfying the classes contract. Your way allows one to construct an invalid object at runtime. – jax Apr 01 '16 at 11:57
  • 1
    Uh I see what you mean now, but your method allows null values also. Better to be upfront with the object contract rather than making the programmer guess. Have a look at the builder pattern in the book called "Effective Java 2nd edition" . – jax Apr 01 '16 at 12:03
  • Builder cannot construct invalid object at runtime, because it uses an all-arguments constructor created by Lombok. This constructor does null checks for parameters annotated with `@NonNull`. This includes parameters that you never specified explicitly via builder methods. – Anton Koscejev Apr 02 '16 at 22:57
  • 1
    `@NotNull` will be evaluated at runtime! – jax Apr 03 '16 at 07:14
  • 7
    You can construct null fields with the previous answers too like Person.builder(null).lastName("John").build(); so you will still need a runtime check no matter what. – Lakatos Gyula May 11 '18 at 17:43
  • 2
    @LakatosGyula, I do agree, but I think that by convention, developers have a smaller chance of calling the required fields with null, you will probably have more mistakes using a regular builder, it's a tradeoff, and I'd go with Anton's answer. – David Barda Apr 11 '20 at 16:19
  • @DavidBarda The only way the custom builder-method approach reduces errors is forcing the caller to pass a value, e.g. they can not miss calling a mandatory setter method. Regardless it will still need null checks. Setting constants into a builder is a very rare use case outside classroom. I don't think it's worth trading in the consistency and readability. – Torben May 11 '21 at 09:47
35

Taking Kevin Day's answer a step further:

@Builder
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE) // If immutability is desired
@ToString
public class Person {
    @NonNull // Presumably name cannot be null since its required by the builder
    private final String name;
    private final String surname;

    private static PersonBuilder builder() {
        return new PersonBuilder();
    }

    public static PersonBuilder builder(String name){
        return builder().name(name);
    }

}

It's not ideal, but it provides compile time enforcement and callers of this class will have exactly one builder method to use.

The Gilbert Arenas Dagger
  • 12,071
  • 13
  • 66
  • 80
  • 3
    fine, but it's still possible to override the value (by mistake) by calling `.name()` so we would have: `Person.builder("john").surname("smith").name("mark").build();` and of course ultimately we would get `mark` not desired `john`. I know that it is a little far-fetched case but possible, and perfect solution would avoid that situtation. Do you think it is possible to defend against that ? (I'm dealing with that right now) – user3529850 Oct 27 '18 at 17:34
  • 1
    @user3529850 if you have required fields AND for some reason immutability concerns within the context of your builder, then you are better off just writing your highly custom builder the old fashion way, withoutt Lombok. Note that in the example that I gave, Person IS immutable. You can only set the name exactly one time. What you are doing is setting the name multiple times on the PersonBuilder, which is not intended to be immutable. – The Gilbert Arenas Dagger Oct 28 '18 at 09:59
  • @user3529850 Even with the classic builder approach you can always pass null in the constructor... – Amit Goldstein Jun 16 '19 at 10:24
  • @AmitGoldstein obviously, Java does not have a built-in static code analyzer to detect null values, this has nothing to do with Lombok implementation of Builder pattern – Kronen Nov 26 '21 at 01:09
  • Made a choice for me – Frankie Drake Apr 25 '23 at 09:21
22

Here's another approach:

@Builder()
@Getter
@ToString
public class Person {

    private final String name;
    private final String surname;

    public static PersonBuilder builder(String name){
        return new PersonBuilder().name(name);
    }

    public static void main(String[] args) {
        Person p = Person.builder("John Doe")
                .surname("Bill")
                .build();
    }
}
Kevin Day
  • 16,067
  • 8
  • 44
  • 68
  • I prefer your approach, as it simply overloads the `builder` method making syntax concise and natural. – Jezor Sep 03 '16 at 00:51
  • 9
    The problem with this approach is that builder() is still visible, so it doesn't *really* make the parameters required. By all means, yes, we need to also use the @NonNull annotation - but that's a runtime check - definitely not good enough if one is trying to craft a super intuitive object. It's a shame that there isn't a way to tailor this sort of thing in lombok - even if we could make a way to make the builder() method private, we could then create our own overloaded public builder(...) method with required params. – Kevin Day Nov 09 '16 at 01:12
  • 1
    Apperantly something changed within the last five years, so builder() would not be available anymore with this approach. In contrast to the accepted anwear, this also doesn't give me a non-hidden hiddenBuilder(), which makes this my favourite approach. – Tobias Grunwald Apr 19 '21 at 09:54
  • Thanks for the heads up @TobiasGrunwald - this makes my approach pretty much perfect (though it does give me a little concern about backwards compatibility for anyone who was still accessing the no-arg builder method!) – Kevin Day Apr 19 '21 at 22:29
18

The simpliest solution is to add a @lombok.NonNull to all mandatory values. The Builder will fail to build the object when mandatory fields are not set.

Here's a JUnit test to demonstrate the behavior of all combinations of final and @NonNull:

import static org.junit.Assert.fail;

import org.junit.Test;

import lombok.Builder;
import lombok.ToString;

public class BuilderTests {
    @Test
    public void allGiven() {
        System.err.println(Foo.builder()
            .nonFinalNull("has_value")
            .nonFinalNonNull("has_value")
            .finalNull("has_value")
            .finalNonNull("has_value")
            .build());
    }

    @Test
    public void noneGiven() {
        try {
            System.err.println(Foo.builder()
                .build()
                .toString());
            fail();
        } catch (NullPointerException e) {
            // expected
        }
    }

    @Test
    public void nonFinalNullOmitted() {
        System.err.println(Foo.builder()
            .nonFinalNonNull("has_value")
            .finalNull("has_value")
            .finalNonNull("has_value")
            .build());
    }

    @Test
    public void nonFinalNonNullOmitted() {
        try {
            System.err.println(Foo.builder()
                .nonFinalNull("has_value")
                .finalNull("has_value")
                .finalNonNull("has_value")
                .build());
            fail();
        } catch (NullPointerException e) {
            // expected
        }
    }

    @Test
    public void finalNullOmitted() {
        System.err.println(Foo.builder()
            .nonFinalNull("has_value")
            .nonFinalNonNull("has_value")
            .finalNonNull("has_value")
            .build());
    }

    @Test
    public void finalNonNullOmitted() {
        try {
            System.err.println(Foo.builder()
                .nonFinalNull("has_value")
                .nonFinalNonNull("has_value")
                .finalNull("has_value")
                .build());
            fail();
        } catch (NullPointerException e) {
            // expected
        }
    }

    @Builder
    @ToString
    private static class Foo {
        private String nonFinalNull;

        @lombok.NonNull
        private String nonFinalNonNull;

        private final String finalNull;

        @lombok.NonNull
        private final String finalNonNull;
    }
}
Brandon Dyer
  • 1,316
  • 12
  • 21
lilalinux
  • 2,903
  • 3
  • 43
  • 53
8

This is my solution for the problem

import lombok.Builder;
import lombok.Data;
import lombok.NonNull;

@Data
@Builder(builderMethodName = "privateBuilder")
public class Person {
    @NonNull
    private String name;
    @NonNull
    private String surname;
    private int age;//optional

public static Url safeBuilder() {
    return new Builder();
}

interface Url {
    Surname name(String name);
}

interface Surname {
    Build surname(String surname);
}

interface Build {
    Build age(int age);
    Person build();
}

public static class Builder implements Url, Surname, Build {
    PersonBuilder pb = Person.privateBuilder();

    @Override
    public Surname name(String name) {
        pb.name(name);
        return this;
    }

    @Override
    public Build surname(String surname) {
        pb.surname(surname);
        return this;

    }

    @Override
    public Build age(int age) {
        pb.age(age);
        return this;
    }

    @Override
    public Person build() {
        return pb.build();
    }
    }
}

inspired by this blog post:

https://blog.jayway.com/2012/02/07/builder-pattern-with-a-twist/

kozla13
  • 1,854
  • 3
  • 23
  • 35
3

Take User class as example, id field is required:

@AllArgsConstructor(access = AccessLevel.PRIVATE) // required, see https://stackoverflow.com/questions/51122400/why-is-lombok-builder-not-compatible-with-this-constructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
public class User {
    private String id;
    private String name;
    private int age;

    public static UserBuilder builder(final String id) {
        return new UserBuilder().id(id);
    }
}

You can only initialize a User instance by builder like User user = User.builder("id-123").name("Tom").build;. With private no args constructer, you are not able to User user = new User(); or User user = new User("id-123"); so you always need to pass the required parameter id. Please note the initialized instance is immutable.

coderz
  • 4,847
  • 11
  • 47
  • 70
  • 1
    But i guess you can still call UserBuilder's default builder with no arguments, no one is stopping it. – Shadman R Jul 14 '21 at 20:13
3

Combining the answer from @Pawel and comment from Max ...

import lombok.Builder;
import lombok.ToString;

@Builder
public class Person {

  private String name;
  private String surname;

  public static PersonBuilder builder(String name) {
    return new PersonBuilder().name(name);
  }
}
codemano
  • 33
  • 1
  • 1
  • 2
    Be aware that this, unfortunately, doesn't work with `@SuperBuilder`, since the generated builder class is also abstract. – Marv Dec 05 '20 at 13:36
2

Here is an inspiration of Pawel response, with an hidden generated builder :

import lombok.Builder;
import lombok.ToString;

@Builder(builderMethodName = "")
@ToString
public class Person {

    private String name;
    private String surname;

    public static PersonBuilder builder(String name) {
        return new PersonBuilder().name(name);
    }
}
1

If you need this functionality, you can customize the builder class by yourself and you can still add @Builder Annotation.

@Builder
public class Person {

    public static class PersonBuilder {
        private String name;

        private PersonBuilder() {
        }

        public PersonBuilder(final String name) {
            this.name = name;
        }
    }

    private static PersonBuilder builder() {
        return null; // or we can throw exception.
    }

    public static PersonBuilder builder(final String name) {
        return new PersonBuilder(clientId);
    }
}
Krishna M
  • 1,135
  • 2
  • 16
  • 32
1

Best Practice:

import lombok.Builder;
import lombok.NonNull;

@Builder(builderMethodName = "privateBuilder")
public class Person {
    @NonNull
    private String name;
    private String surname;

    public static class PersonNameBuilder {
        public PersonBuilder name(String name) {
            return Person.privateBuilder().name(status);
        }
    }

    public static PersonNameBuilder builder(String name) {
        return new PersonNameBuilder();
    }

    private static PersonBuilder privateBuilder(){
        return new PersonBuilder();
    }
}

Usage:

PersonNameBuilder nameBuilder = Person.builder();
PersonBuilder builder = nameBuilder.name("John");
Person p1 = builder.surname("Smith").build();

// Or
Person p2 = Person.builder().name("John").surname("Smith").build();
dallaslu
  • 444
  • 4
  • 7
1

As much as I would like to have the compile time validation feature, the authors of the library had made it clear that the feature probably won't be added.

So my take on this is, to have something like this.

@Builder
public class Person {
  String name;
  Integer age;
  Optional optional;

  @Builder
  public class Optional {
    String surname;
    String companyName;
    String spouseName;
}

}

And you can use it like

 Person p = Person.builder()
            .age(40)
            .name("David")
            .optional(Person.Optional.builder()
                    .surname("Lee")
                    .companyName("Super Company")
                    .spouseName("Emma")
                    .build())
            .build();

No, there's no validation. But from the library's users point of view, it's pretty clear what's required and what's not and be able to build an object instance without looking at the documentation.

iecanfly
  • 638
  • 7
  • 15
0

In order to make limitations and risks implied by lombok's builder implementation as obvious as possible and therefore reduce the probability of erroneous abuse, I propose the following solution:

import lombok.Builder;

@Builder(builderClassName = "UnsafeBuilder")
public class Person {
    private String name;
    private String surname;

    public static UnsafeBuilder builder(String name) {
        return new UnsafeBuilder().name(name);
    }
}

Using this solution as intended is straightforward:

Person thePerson = Person.builder("the_name").surname("the_surname").build();

The only way to use the builder in an unintended way makes the risk obvious, and most probably won't be chosen by mistake:

final Person unsafePerson = new Person.UnsafeBuilder().surname("the_surname").build();

The class name could of course be chosen even more radically - for example NeverCallThisConstructor.

yaccob
  • 1,230
  • 13
  • 16
0

What about this approach?

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true, fluent = true)
public class Person {
    private final String name; // 'final' prevents lombok from creating a setter method
    private String surname;

    public Person(String name) {
        this.name = name; // mandatory
        this.surname = null; // optional, with default value of 'null'
    }

    public static void main(String[] args) {
        Person gabi = new Person("Gabriela");
        Person smith = new Person("John").surname("Smith");
        System.out.println(gabi);
        System.out.println(smith);
    }
}

Executing main() will print:

Person(name=Gabriela, surname=null)
Person(name=John, surname=Smith)

The combination of

@Data
@Accessors(chain = true, fluent = true)

Makes lombok create setters that return this instead of void, and the getters and setters will be named as the property names, without 'get' and 'set' prefix.

There will be a setter for surname(), but because name is final, there won't be a setter for name.

I guess this implemenation correctly accomodates the builder pattern when there are mandatory and optional fields.

Bruno Negrão Zica
  • 764
  • 2
  • 7
  • 16