Required arguments with a Lombok @Builder

JavaLombok

Java Problem Overview


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;
}

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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

Solution 3 - Java

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.

Solution 4 - Java

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

Solution 5 - Java

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;
	}
}

Solution 6 - Java

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/

Solution 7 - Java

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.

Solution 8 - Java

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

Solution 9 - Java

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

Solution 10 - Java

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

Solution 11 - Java

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.

Solution 12 - Java

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

Solution 13 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjaxView Question on Stackoverflow
Solution 1 - JavaPawelView Answer on Stackoverflow
Solution 2 - JavaAnton KoscejevView Answer on Stackoverflow
Solution 3 - JavaThe Gilbert Arenas DaggerView Answer on Stackoverflow
Solution 4 - JavaKevin DayView Answer on Stackoverflow
Solution 5 - JavalilalinuxView Answer on Stackoverflow
Solution 6 - Javakozla13View Answer on Stackoverflow
Solution 7 - JavacoderzView Answer on Stackoverflow
Solution 8 - JavacodemanoView Answer on Stackoverflow
Solution 9 - JavaKrishna MView Answer on Stackoverflow
Solution 10 - JavadallasluView Answer on Stackoverflow
Solution 11 - JavaiecanflyView Answer on Stackoverflow
Solution 12 - JavaPhilippe EscureView Answer on Stackoverflow
Solution 13 - JavayaccobView Answer on Stackoverflow