Can I use the Lombok @Builder passing the parent class as parameter?

Viewed 2536

I want to create a new Child instance passing a Parent and other additional parameters.

For example if I have:

public class Parent {
    public String param1;
    public String param2;
    // many parameters
    public String paramN;
}

public class Child extends Parent {
    public String subValue;
}

With lombok, is there a builder that lets me create a Child instance passing the Parent and the missing value as parameters?

Would be easier if I could write something like:

Parent p = Parent.builder()
                 .param1("a")
                 .param2("b")
                 // many parameters
                 .paramN("b")
                 .build();
Child c = Child.builder(p).subValue("c").build();
3 Answers

Other answers don't truly make your client code simply reuse the parent instance you already have. But this is doable. You have two options:

The hard one is to write your custom annotation that does what you want. You can even make it generic so that it works for any classes the have parent/child hierarchy. Have a look at this example. If you feel brave you can raise a feature request on Lombok's github page.

Option two would be to write your custom builder for the child. See example here. In your custom builder in the init step you would be reading a passed in Parent instance, and setup the inherited fields only.

The regular @Builder is not sufficient here, because you are dealing with a class hierarchy. However, @SuperBuilder was made exactly for such a case.

@SuperBuilder generates complex code loaded with generics. That makes this solution difficult to understand without in-depth knowledge about the code @SuperBuilder generates. You should think about whether this is worth it.

Here's the solution (with Lombok >= 1.18.16):

@SuperBuilder(toBuilder = true)
public static class Parent {
    public String param1;
    public String param2;
    // many parameters
    public String paramN;

    public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
        protected B $fillValuesFromParent(Parent instance) {
            $fillValuesFromInstanceIntoBuilder(instance, this);
            return self();
        }
    }
}

@SuperBuilder(toBuilder = true)
public static class Child extends Parent {
    public String subValue;

    public static ChildBuilder<?, ?> toBuilder(Parent p) {
        return new ChildBuilderImpl().$fillValuesFromParent(p);
    }
}

The new toBuilder method on Child creates a new ChildBuilderImpl (which will create a Child instance when calling build()). To fill the values from the given Parent p, it calls the new $fillValuesFromParent method from ParentBuilder. This method further delegates the call to the method $fillValuesFromInstanceIntoBuilder, which is generated by Lombok and performs the actual copying of the field values to the new builder instance.

Also note the $ prefix on the methods. This basically says: I'm an implementation detail; don't use me unless you know what you are doing, I might break on the next Lombok version without further notice.

I would suggest you use @SuperBuilder

@SuperBuilder was introduced as experimental feature in lombok v1.18.2. 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.

@Getter
@SuperBuilder
public class Parent {
    public String name;
    public String value;
}

@Getter
@SuperBuilder
public class Child extends Parent {
    public String subValue;
}

Then all you need to do is

Child.builder().name("a").value("b").subValue("c").build();
Related