Mandatory builder fields

3,124 views
Skip to first unread message

Greg Soulsby @ model drivers

unread,
Nov 19, 2013, 1:31:45 PM11/19/13
to project...@googlegroups.com
My class has some fields / attributes which are mandatory. I want the builder fail if the programmer assigns a null value, or more likely forgets to set a mandatory value? 

Are the some clever approach to implement this?

I am using @Value as well as @Builder

Reinier Zwitserloot

unread,
Nov 19, 2013, 2:16:01 PM11/19/13
to project-lombok
You can't make the builder itself fail-immediately when attempting to 'set' one of the fields to null, nor can you make it so that the build() method is not accessible until you've done so. However, you CAN make the build() method throw an exception. This will not change; the vast majority of @Builder usecases one-liner it or at least follow up 'setting' calls with a build() call, therefore we will not complicate the @Builder mechanic by trying to hack in support for at-set-time constraint checking.

Therefore, manually write out the whole constructor and add whatever null-checks you deem necessary, then annotate this constructor (and not the class) with @Builder, or:

Just use @NonNull on your fields, which will make lombok add null-checks in the generated constructor. In turn, this means the build() method will fail with named NPEs (the error message of the NPE will be equal to the field name).

 --Reinier Zwitserloot


--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Greg Soulsby @ model drivers

unread,
Nov 22, 2013, 9:02:31 AM11/22/13
to project...@googlegroups.com
Thanks, the @NonNull is very helpful

Ryan McKay

unread,
Jan 17, 2014, 10:23:39 AM1/17/14
to project...@googlegroups.com
Would you consider allowing to specify a PostBuildMethod= element in the @Builder.  This would identify a method on the built object to be called before returning from the build() method.  This would be very handy for adding validation, defaults, etc, without having to implement and maintain a constructor.

Reinier Zwitserloot

unread,
Jan 17, 2014, 10:40:40 AM1/17/14
to project...@googlegroups.com
Probably not; if you want this, why not create a static method that wraps your constructor, and put @Builder on that:

@Builder private static checkAndCreate(String name, int age) {
    if (name.isEmpty()) throw new IllegalArgumentException("name cannot be empty.");
    if (age == 0) age = 30;
    return new Person(name, age);
}

Now this is effectively your 'postBuildMethod'.

Ryan McKay

unread,
Feb 6, 2014, 12:46:22 PM2/6/14
to project...@googlegroups.com
Thanks for the response.  The main argument I have against the approach you described is that now you have to maintain it to add/remove args to match the bean data members.  That is part of the boilerplate code we are trying to eliminate by using @Builder.  This is not the case with an instance method on the built object.  If for example the post build method was something like: 

private void validate() {
    Set<ConstraintViolation<Object>> violations = jsr303validator.validate(this);
    if (!violations.isEmpty()) {
        throw new ConstraintViolationException(violations);
    }
}

Then all you need to do to add another validated data member is, e.g.,
@NotNull dataMember;

Thanks

Reinier Zwitserloot

unread,
Feb 6, 2014, 2:33:55 PM2/6/14
to project-lombok
That's a fair argument, but the downside of glueing a hack faux language in the form of annotations to handle validation is in my opinion a far larger downside compared to having to change the arguments to the static method whenever you change the fields in the buildable class.

It gets a lot better when using an existing framework, but at that point we're going to have to do something fancy with the lombok.extern package, and it becomes a lot more niche.

 --Reinier Zwitserloot


--

Martin Grajcar

unread,
Feb 6, 2014, 2:38:14 PM2/6/14
to project...@googlegroups.com
Even when writing your own ctor, you still save a lot by using builder.

I could also use some validation in the generated constructor and proposed an annotation for it:

To my surprise, nobody cared to replay. It should be pretty simple, all lombok would do is to call the method inside of the generated code.

sirvaskur

unread,
Jun 17, 2014, 3:16:08 PM6/17/14
to project...@googlegroups.com
Nice to see other people here who had the same idea like me. :) I'd love to have a hook in the generated build method like @Builder(preBuild="myFunctionName") to have a chance to avoid illegal states. Writing an own constructor completely removes the whole benefit of the builder annotation as it's a very error-prone process for classes with many fields. Just imagine some colleague adding a field without noticing the necessary change in the constructor.

Reinier Zwitserloot

unread,
Jun 17, 2014, 9:47:40 PM6/17/14
to project-lombok
Stringly typed refs to methods are extremely ugly, it's a last resort, and this use case is not nearly important enough to warrant stooping _that_ low. If anything, there'd be a @Builder.Prebuild kind of method.

More to the point, what does this actually mean? Please show me what lombok would desugar it to, because I don't really get it. You are entirely free to make your own builder class and leave it blank (letting lombok fill in all the fields and methods), except for the build() method, which does all your checks and then manually calls the 'target' (static method or constructor). This is some manual busywork, but it's not possible for a colleague to add a method to the constructor/static method you are putting @Builder on without immediately running into a compile-time error.

There's some boilerplate involved but it avoids stringly typed refs and thus wins that match handily.


 --Reinier Zwitserloot


--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Martin Grajcar

unread,
Jun 17, 2014, 10:11:22 PM6/17/14
to project...@googlegroups.com
On Tue, Jun 17, 2014 at 9:16 PM, sirvaskur <sirv...@gmail.com> wrote:
Nice to see other people here who had the same idea like me. :) I'd love to have a hook in the generated build method like @Builder(preBuild="myFunctionName") to have a chance to avoid illegal states.

Do you need it to happen before the constructor is called, or would https://code.google.com/p/projectlombok/issues/detail?id=639
suffice? Actually, I can't imagine any situation when the @Validator wouldn't do. 

On Wed, Jun 18, 2014 at 3:47 AM, Reinier Zwitserloot <rei...@zwitserloot.com> wrote:
Stringly typed refs to methods are extremely ugly, it's a last resort, and this use case is not nearly important enough to warrant stooping _that_ low. If anything, there'd be a @Builder.Prebuild kind of method.

More to the point, what does this actually mean? Please show me what lombok would desugar it to, because I don't really get it. You are entirely free to make your own builder class and leave it blank (letting lombok fill in all the fields and methods), except for the build() method, which does all your checks and then manually calls the 'target' (static method or constructor). This is some manual busywork, but it's not possible for a colleague to add a method to the constructor/static method you are putting @Builder on without immediately running into a compile-time error.

A problem could only occur when someone swapped two (assignment-compatible) fields, but this is pretty rare.

sirvaskur

unread,
Jun 19, 2014, 4:40:30 AM6/19/14
to project...@googlegroups.com
Oh, yeah, the @Validator seems like a good solution to me!

Sébastien Dubois

unread,
Feb 25, 2015, 12:17:41 PM2/25/15
to project...@googlegroups.com
I see Project Lombok as an ideal candidate to generate builders which force calling all setter methods at compilation time, instead of throwing exceptions. Having exceptions would still require boilerplate code to handle them.

I would expect to write:

@Builder
public class MyClass {
@Mandatory private long longField;
@Mandatory private String stringField;
private int intField;
private char charField;
}

And Lombok would generate:

public class MyClass {

private final long longField;
private final String stringField;
private final int intField;
private final char charField;

private MyClass(long longField, String stringField, int intField, char charField) {
this.longField = longField;
this.stringField = stringField;
this.intField = intField;
this.charField = charField;
}

public long getLongField() {
return longField;
}

public String getStringField() {
return stringField;
}

public int getIntField() {
return intField;
}

public int getCharField() {
return charField;
}

public interface LongFieldBuilder {
StringFieldBuilder longField(long longField);
}

public interface StringFieldBuilder {
OptionalsBuilder stringField(String stringField);
}

public interface OptionalsBuilder {
Builder intField(int intField);
Builder charField(char charField);
}

public static LongFieldBuilder myClass() {
return new Builder();
}

private static class Builder implements LongFieldBuilder, StringFieldBuilder, OptionalsBuilder {

private long longField;
private String stringField;
private int intField;
private char charField;

@Override
public StringFieldBuilder longField(long longField) {
this.longField = longField;
return this;
}

@Override
public OptionalsBuilder stringField(String stringField) {
this.stringField = stringField;
return this;
}

@Override
public Builder intField(int intField) {
this.intField = intField;
return this;
}

@Override
public Builder charField(char charField) {
this.charField = charField;
return this;
}

public MyClass build() {
return new MyClass(longField, stringField, intField, charField);
}
}
}

An example usage would be myClass().longField(1L).stringField("blah").charField('c').build();

(Although unrelated, implementation-wise, it is further possible to get rid of fields in the builder itself and assign values directly to the object's field, by making them non-final and making the builder class non-static.)
Message has been deleted

Reinier Zwitserloot

unread,
Feb 25, 2015, 10:48:38 PM2/25/15
to project-lombok
You've not only made them mandatory, you've also enforced the ordering with this; you must first set field X, and only X can be set, and until you've done that, you get no assistance on what else you can do, and then you must set field Y, and only Y, and only then do you get to set any optionals, as well as call build().

This also creates a pile-up of public builder classes with extremely convoluted names in order to ensure they don't clash. For example, "StringFieldBuilder" in this example would preclude adding builder to 2 different things in the same class (and as builder is actually shorthand for 'please make a fluent API for this static method with a convoluted parameter list', that's a problem), and furthermore it is simply a misnomer, and doesn't provide any information. Once you get past the mandatories, all builders are always called 'OptionalsBuilder'. Not very descriptive.

It's an interesting idea but far too exotic to add to lombok at this time.

How often have you _actually_ run into the problem that code failed in production because you forgot to set a mandatory field? When you did get that wrong, how long did it take you to find the bug? and fix the bug?

I bet the answers are: "Never", "1 minute", "2 minutes".

 --Reinier Zwitserloot

On Wed, Feb 25, 2015 at 2:49 PM, Sébastien Dubois <sedu...@gmail.com> wrote:
I see Project Lombok as ideal to generate builders which force the setting of fields at compilation time, instead of throwing exceptions. Throwing exceptions means that boilerplate code is still required to handle them.

I would expect to write something like the following:

@Builder
public class MyClass {
@Mandatory private long longField;
@Mandatory private String stringField;
private int intField;
  private final char charField;
}

Which Lombok would convert in something similar to the code below, and would be callable with e.g. myClass().longField(1L).stringField("blah").charField('c').build(). Failing to call e.g. stringField() would make compilation fail, but e.g. not calling intField() is permitted.

(Implementation-wise, if you get rid of the final modifier on MyClass fields and make the builder class non-static, it is further possible for the builder to directly assign values to them instead of declaring fields inside the builder itself (and the all-args constructor could be deleted).)
Le mardi 19 novembre 2013 21:16:01 UTC+2, Reinier Zwitserloot a écrit :
For more options, visit https://groups.google.com/d/optout.

Sébastien Dubois

unread,
Feb 26, 2015, 6:17:17 AM2/26/15
to project...@googlegroups.com
Indeed, ordering is then mandatory. I could ask how often do you _actually_ need a free ordering? :) In my case, the answer is never. As long as this restriction would be documented and as long as the user has the choice of using this functionality or not, I see no problem. On the other hand, I believe it is only with a system such as Lombok's that such a builder system would be practically usable.

As to the second argument, this is an issue which would be internal to the library and which is resolvable (naming could be made unique to each created builder). Of course, better names could be chosen for the builder interfaces, to avoid the misnomer issue you described. The provided example is a proof of concept, nothing else.

As to the last argument, you answered yourself (19th Nov 2013) that people sometimes want to protect their builder against forgotten mandatory setters. In my own company, this question often arises in pull requests. Whatever the answer to that, the fact that this topic comes back regularly means that having compilation errors would be very helpful to concentrate on more interesting topics, and it would be much cleaner for the developer than having to add exception-handling.

There would be implementation issues to be adressed, and there are potential drawbacks such as additional classes in memory and forced method order, but these should be compared to the advantage of having compilation-time errors. As long as users are free to choose, I would say it is for them to experiment and decide. Even if the need and practicality of this idea is not demonstrated, to me this does look like a good Lombok experimental feature candidate.

Thanks for your feedback,

Sébastien Dubois


--
You received this message because you are subscribed to a topic in the Google Groups "Project Lombok" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/project-lombok/TK0Ud8ojcmc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to project-lombo...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages