The declaration ChildBuilder extends BaseBuilder<ChildBuilder> somehow indicates a code smell and seems to violate DRY. In this example BaseBuilder can be parametrized only with ChildBuilder and nothing else, so it should be redundant.
I would rather rethink whether I really want to over-architecture this and I would try to put all the methods from child builders into the BaseBuilder. Then I can simply return this from all the methods supporting chaining.
If I still think that I will benefit by separating specific groups of builder methods into their own classes, then I would give preference to composition, because applying inheritance only for code reuse is not recommended.
Suppose we have two subclasses of the BaseBuilder:
class BuilderA extends BaseBuilder<BuilderA> { BuilderA buildSomethingA() { return this; } } class BuilderB extends BaseBuilder<BuilderB> { BuilderB buildSomethingB() { return this; } }
What if the need arises to chain buildSomethingA and buildSomethingB like:
builder.buildSomething().buildSomethingA().buildSomethingB();
We will not be able to do it without moving the subclass methods to the BaseBuilder; but imagine there is also BuilderC for which those methods don't make sense and shouldn't be inherited from the BaseBuilder.
If we nevertheless move these two methods to the superclass, and next time three other methods and next time... we'll end up with a superclass responsible for 90% of the duties of the entire hierarchy with plenty of code like:
if ((this instanceof BuilderB) && !flag1 && flag2) { ... } else if ((this instanceof BuilderC) && flag1 && !flag2 && thing != null) { ... } else if ...
The solution I like more is a DSL like:
builder.buildSomething1().buildSomething2() .builderA() .buildSomethingA1().buildSomethingA2() .end() .buildSomething3() .builderB() .buildSomethingB() .end();
Here end() returns the builder instance so you can chain more of its methods or start a new sub-builder.
This way the (sub)builders can inherit from whatever they need to (otherwise they must extend only the BaseBuilder) and can have their own meaningful hierarchies or compositions.
BaseBuilder<String>wouldn't make sense.