Your question isn't entirely clear; but I think the answer to your question is very very simple.
The simple case
The only problem is what I call a 'doctor it hurts when I smash this hammer into my face' problem. You're making your own life hard for no reason and the solution is simply: Stop doing that.
Lombok makes all the infrastructure for you. There is no need to also make a constructor that takes the builder and then write by hand all the code to copy the values in the builder into your own fields.
Forget about lombok for a moment. Just think of the general principle of builders. There needs to be some method somewhere that does the job of actually 'constructing' - it will take all those non-final fields in the builder type and set the (usually) final fields in the actual class to these values.
There are 2 different takes on where this code should go. One option is that the main class has a 'simple' constructor (one that takes every field as parameter and just has a bunch of this.fieldName = fieldName;
statements in it), and the builder class has a method that does the hard work. The second take is that the main class has only a constructor that accepts an instance of the builder and it does the work.
The first concept is slightly more popular than the second.
Lombok only supports the first. This isn't a problem: It's an implementation detail (that constructor, regardless of which 'take' on the idea you want, should be private, hence, it just doesn't matter; which choice you make is invisible to all code outside of that source file anyway).
Lombok already generates the second mode: There's a build()
method in the generated builder class that does the work, there is no need to write a constructor that also does it. In fact, that's actively bad, because now you get endless, pointless style debates; should one call Person.builder().name("Jane").age(40).build();
or should one call new Person(Person.builder().name("Jane").age(40));
? You shouldn't introduce 2 ways of doing a thing if there are no clear alternate use cases where one is better than the other for both alternatives.
Thus, the solution is very very simple: Just.. delete that constructor. Done. If you have new Person(Person.builder()...);
anywhere in your code base, that'll now have an error. Fix it by simply removing the new Person()
part and tossing a .build()
at the end of the build chain. Easy.
You've removed a bunch of code from your source files and your API is now better. Double win!
The complex case
It's not at all in your snippets, but it's possible you really do need other internal code (as in, in the same source file, as the builder's stuff is all private
) to have access to the map of a builder 'mid flight' (after being made but before being used to create an instance).
Lombok simply does not support this. The reasons for this are somewhat complex and mostly spelled out in the documentation. Oversimplifying a bit: Because it's more efficient this way.
Once you toss @Singular
on a thing that you also want a @Builder
for, lombok officially washes its hands: The code it generates is no longer designed to be 'directly interacted with / interoperated with' by the programmer at all. You let lombok do its thing, and if lombok's thing is no good, you don't use that feature of lombok. (see docs - or just take it from me, I'm a core contributor to the project).
Hence, we've ended up in a place you usually end up when you take lombok features and need it to go places that are fairly far away from the well-trodden path: Lombok is a tool to generate boilerplate. What you want is sufficiently uncommon that it has fully left the realm of 'boilerplate', and therefore, lombok's "mission statement" no longer applies to your case. If lombok can cater to your needs 'for free' (without complicating the tool for other users and without causing significant maintenance issues) we'd do that, but likely it can't - as it is here. Allowing the @Singular
'map' to be accessible mid-build has a cost, and any but the tiniest of costs is unacceptable given that lombok isn't meant to cater to your needs.
To be clear, I do not think this applies to your scenario (the first, simple case does - simply get rid of that constructor). But, if someone has a valid use case for accessing that map mid-build, the answer is: Get rid of @Builder
and write it by hand; possibly use java -jar lombok.jar delombok
to give you a basic skeleton to work off of.