Thanks for your thoughts, Artem Abeleshev!
I agree, although I think that objects with a lot of optional fields may indicate a conceptual problem in the codebase. Maybe the object needs to be split up. Sometimes you'll see that some optional fields are always filled in together. Maybe that can be reworked to a grouped concept of those fields. In the end, it all depends on the context of the application. :-)
What I like about constructors with many parameters is that it points out that there is a code smell. It screams to be refactored. A builder might hide that.
I think your solution for mandatory fields, together with builders, is a good one. Great call!
Builders usually used to built immutable objects. Like you mentioned it is possible to use constructors for this purposes. But builders come handy when your immutable object contains a bunch of optional fields. In case of constructors you will end up with telescoping constructors. Exposing an all args constructor is a bad practice if you have quite a bunch of fields (usually more than 6), it looks bad and bulky. This is where builder pattern come to rescue.
Problem of inconsistent object is not a problem of the builder usage, it is a probem of the object itself. You will anyway end up applying some checks if state of your object depends on the args values. Even the required fields can be passed as
nullthat is equivalent to not setting them at all. Builder has nothing to do with that. If some invalid parameter passed, anIllegalArgumentExceptionshould be thrown. You can make it either in construction, or in abuildmethod of builder.If you want to guarantee that some mandatory args will be always indicated by caller, you can make builder constructor to accept them. I usually do private constructor for builder and add static factory method for builder itself:
public static class Builder { public static Builder of(Integer arg1, String arg2) { return new Builder(arg1, arg2); } private Integer arg1; private String arg2; private Foo arg3; private Bar arg4; private Builder(Integer arg1, String arg2) { this.arg1 = arg1; this.arg2 = arg2; } // other builder methods }