Lombok's builder with mandatory parameters
Asked Answered
E

4

9

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 mandatory. In this case, the name field is mandatory. Ideally, I would like to declare it like so.

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

When googling i found many alternatives like overriding the builder implementation as below:

@Builder
public class Person {

    private String name;
    private String surname;

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

And then use it like below:

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

The problem with above approach is that it still provides the name() and PersonBuilder() method like below, which i don't want:

Person p = Person.builder("Name").surname("Surname").name("").build();
Person p = new Person.PersonBuilder().build;

Another approach is to add @lombok.nonnull check at name which will force to provide value for name while creating object. but it is a runtime check. it will not force me to provide value for name while creating object.

Is there any additional technique which lombok provides to achieve below:

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

Note: The builder() and name() should not be exposed. The only way to create Person object should be either above or below:

 Person p = Person.builder("Name").build();
Endaendall answered 28/7, 2020 at 6:28 Comment(0)
S
3

You can't really do it with lombok, see the explanation from the library authors. But is it that complicated to roll this builder on your own?

public static class PersonBuilder {

    private final String name;
    private String surname;

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

    public PersonBuilder surname(String surname) {
        this.surname = surname;
        return this;
    }

    public Person build() {
        return new Person(name, surname);
    }
        
}

with the same method that you already have:

    public static PersonBuilder builder(String name) {
        return new PersonBuilder(name);
    }
Swampy answered 28/7, 2020 at 13:41 Comment(0)
E
1

Try to make the builder private.

Did you check this comment Required arguments with a Lombok @Builder

I am pretty sure you will find out once read the thread one more time.

P.S. If you have a class with only two field better use directly a constructor.

Execratory answered 28/7, 2020 at 13:52 Comment(2)
Thanks for the response. I checked the thread and i found that few things were unanswered. Making the builder private solved one of the issue. but my another concern is that the name() method is visible and one can use it like below: Person.builder("Name").surname("Surname").name("").build(); in this case the value provided in the builder() will be changed.Endaendall
@Endaendall I believe you have to write own builder and not use Lombok for your specific case as it seems, fields are still public.Execratory
M
0

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(name);
        }
    }
    
    private static class PersonExtraBuilder extends PersonBuilder{
        @Deprecated
        @Override
        public PersonBuilder name(String name) {
            return this;
        }
    }

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

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

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();

// The last `.name("")` will not work, and it will be marked as Deprecated by IDE.
Person p3 = Person.builder().name("John").surname("Smith").name("").build();
Maverick answered 26/5, 2021 at 3:12 Comment(0)
A
0

It is probably too late for OP, but for anyone else still searching for a way, I recommend you doing this:

@Builder(builderClassName = "Builder")
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Person {

  private final String name;
  private final String surname;

  public static class Builder {
    public Person build() {
      Objects.requireNonNull(name);
      return new Person(name, surname);
    }
  }
}

The thing is, lombok actually lets you declare parts of your builder manually. In this case I redefined the build method and used Objects.requireNonNull to make sure the required fields have been assigned.

The downsides to this is that the check is not compile time and you have to code the build method yourself.

However I find this be much better than all quirks with custom static methods, because those can be easily bypassed and allow violating your class contract.

Aloft answered 17/7, 2024 at 12:58 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.