javalombok

Extending a lombok builder without breaking chaining


I have a class with a builder pattern that extends a class also with a builder (using lombok @SuperBuilder).

It works fine, but the problem is that return type of methods in the parent builder are naturally of the parent builder - which means that chains breaks in the actual usage of the builders if I use any parent builder methods before any child builder methods.

@Builder
public class Activity {
    protected String foo;
    protected String bar;

    public static abstract class ActivityBuilder {
        public ActivityBuilder baz(String baz) {
            // ... do something
            return this;
        }
    }
}


@SuperBuilder
public class DetailedActivity extends Activity {

    public static abstract class DetailedActivityBuilder extends ActivityBuilder {
        public DetailedActivityBuilder platform(Platform platform) {
            this.foo(platform.foo);
            this.bar(platform.bar)
            return this;
        }
    }
}

// Works because "DetailedActivityBuilder" methods come first:
DetailedActivityBuilder.builder().platform(platformData).foo()

// Fails because the parent builder returns ActivityBuilder 
DetailedActivityBuilder.builder().foo().platform(platformData)"

Is there any approach to returning the correct type and allowing chaining to work in any order?

I thought about passing a generic to ActivityBuilder but that only fixes custom methods - lombok generated methods still return the ActivityBuilder type.


Solution

  • It is important that Activity is @SuperBuilder too. From documentation:

    The @SuperBuilder annotation produces complex builder APIs for your classes. In contrast to @Builder, @SuperBuilder also works with fields from superclasses. However, it only works for types. Most importantly, it requires that all superclasses also have the @SuperBuilder annotation.

    Then, the builder classes need to match what lombok would have generated. It should have 2 type parameters, C representing the type that is being built, and B representing the builder type that the builder methods return.

    Your custom methods should return B, and use return self(); at the end, instead of return this;. self() is a method generated by @SuperBuilder.

    @SuperBuilder
    class Activity {
        protected String foo;
        protected String bar;
    
        public static abstract class ActivityBuilder<C extends Activity, B extends ActivityBuilder<C, B>> {
            public B baz(String baz) {
                // ... do something
                return self();
            }
        }
    }
    
    @SuperBuilder
    class DetailedActivity extends Activity {
    
        public static abstract class DetailedActivityBuilder<C extends DetailedActivity, B extends DetailedActivityBuilder<C, B>> extends ActivityBuilder<C, B> {
            public B platform(Platform platform) {
                // do something else...
                return self();
            }
        }
    }