Search code examples
javagenericslombokbuilder

Using Lombok @SuperBuilder Using Generic Final Child Class Does Not Compile


I'm currently facing a problem with Lombok's @SuperBuilder annotation and generics. I have an abstract parent and a final child class. When creating the child using the builder, passing values of the correct type leads to compile errors.

The simplified classes and interfaces

@SuperBuilder(toBuilder = true)
public abstract class Parent<T> {

    private SomeGenericClass<?, Parent<T>> someGenericClass;
    private T value;
}

interface SomeInterface<D> {

}

class SomeGenericClass<D, S> implements SomeInterface<D> {

}

@SuperBuilder(toBuilder = true)
final class Child<T> extends Parent<T> {

}

The test code

public class SuperBuilderTest {

    public static void main(String[] args) {
        SomeGenericClass<String, Parent<Long>> someGenericClass = new SomeGenericClass<>();

        Child<Long> child = Child.builder() 
                .someGenericClass(someGenericClass) // error: The method someGenericClass(SomeGenericClass<?,Parent<Object>>) in
                // the type Parent.ParentBuilder<Object,capture#1-of ?,capture#2-of ?> is
                // not applicable for the arguments (SomeGenericClass<String,Parent<Long>>)
                .value(10L) // error: Type mismatch: cannot convert from capture#1-of ? to Child<Long>
                .build();
    }
}

If works, as soon as the child itself no longer has a generic parameter. So having the child like this, everything compiles and works perfectly fine:

@SuperBuilder(toBuilder = true)
final class Child extends Parent<Long> {

}

Am I doing something wrong or is it not possible to have generic final classes when using the @SuperBuilder?


Solution

  • The type parameter for builder() (which represents what kind of Child you are creating) is not inferred correctly. The compiler thinks it's Object. To fix the error, just make it very clear what kind of Child.Builder you are creating, e.g.

    Child.<Long>builder()
        ...
    

    The reason for why this cannot be inferred is similar to why chaining thenComparings breaks type inference.

    You can see something similar happen with simple builders too:

    @Builder
    final class Foo<T> {
        private T t;
    }
    
    // error here!
    Foo<Long> f = Foo.builder().t(1L).build();
    

    Though in simple cases like that, the error message is a lot clearer, saying that you cannot convert a Foo<Object> to Foo<Long>.

    Your case is more complicated - you have a SomeGenericClass<?, Parent<T>> field. That means someGenericClass() is expecting a SomeGenericClass<?, Parent<Object>>. A SomeGenericClass<String, Parent<Long>> certainly can't be passed to that. And the fact that you're using SuperBuilder makes things even more complicated.