Why @Builder Fails with Inherited Fields and How @SuperBuilder Solves It

This article explains the limitations of Lombok's @Builder annotation when dealing with inherited fields, demonstrates the issue with a sample ItemDTO class, and shows how the newer @SuperBuilder annotation overcomes these constraints by correctly handling superclass members, including code examples and internal implementation details.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why @Builder Fails with Inherited Fields and How @SuperBuilder Solves It

Using Lombok @Builder

Lombok provides a convenient @Builder annotation that generates a builder API for a class. For a typical product model the code looks like:

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ItemDTO {
    /** 商品ID */
    private Long itemId;
    /** 商品标题 */
    private String itemTitle;
    /** 商品原价,单位是分 */
    private Long price;
    /** 商品优惠价,单位是分 */
    private Long promotionPrice;
}

Creating an instance then requires only one line:

ItemDTO itemDTO = ItemDTO.builder()
        .itemId(6542744309L)
        .itemTitle("测试请不要拍小番茄500g/盒")
        .price(500L)
        .promotionPrice(325L)
        .build();
System.out.println(itemDTO);

This style is concise and eliminates boilerplate setter code.

Limitations of @Builder with Inheritance

When a class hierarchy is introduced, e.g., a BaseDTO superclass, the @Builder annotation can no longer set fields declared in the parent class. The child class cannot invoke builder() to populate superclass members.

@Builder
@NoArgsConstructor
public class BaseDTO {
    /** 业务身份 */
    private String bizType;
    /** 场景 */
    private String scene;
}

Applying @Builder to a subclass that extends BaseDTO does not affect the parent fields because the annotation only processes the class it decorates, ignoring its super‑classes.

Try @SuperBuilder Instead

Before Lombok v1.18.2 this problem had no easy solution. Starting with that version, Lombok introduced @SuperBuilder, which generates builders that can handle fields from super‑classes.

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 where all superclasses also have the @SuperBuilder annotation.

To use it, annotate both the parent and child classes with @SuperBuilder:

Now the builder of the subclass can set the parent’s fields as well.

How Lombok Generates Code

Lombok modifies the abstract syntax tree (AST) during compilation via JVM extension points, altering the generated bytecode.

Why @Builder Cannot Access Parent Fields

Looking at Lombok’s source, each annotation has two implementations: one for javac and one for eclipse. In an IDE like IntelliJ the javac implementation is used. The core handling method is lombok.javac.handlers.HandleBuilder#handle:

JavacNode parent = annotationNode.up();
if (parent.get() instanceof JCClassDecl) {
    job.parentType = parent;
    JCClassDecl td = (JCClassDecl) parent.get();
    ListBuffer<JavacNode> allFields = new ListBuffer<>();
    boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent));
    // iterate over fields
    for (JavacNode fieldNode : HandleConstructor.findAllFields(parent, true)) {
        JCVariableDecl fd = (JCVariableDecl) fieldNode.get();
        // ... filter static, final, $‑prefixed fields ...
    }
}

The method findAllFields walks the AST of the current class only, filtering out static fields, final fields (unless allowed), and fields whose names start with $. Consequently, fields declared in a superclass are never visited.

public static List<JavacNode> findAllFields(JavacNode typeNode, boolean evenFinalInitialized) {
    ListBuffer<JavacNode> fields = new ListBuffer<>();
    for (JavacNode child : typeNode.down()) {
        if (child.getKind() != Kind.FIELD) continue;
        JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
        if (fieldDecl.name.toString().startsWith("$")) continue;
        long fieldFlags = fieldDecl.mods.flags;
        if ((fieldFlags & Flags.STATIC) != 0) continue;
        boolean isFinal = (fieldFlags & Flags.FINAL) != 0;
        if (evenFinalInitialized || !isFinal || fieldDecl.init == null) fields.append(child);
    }
    return fields.toList();
}

Viewing the AST of ItemDTO with tools such as JDT AstView confirms that only the four explicitly declared fields appear; inherited fields are absent.

Because findAllFields starts from the current class’s AST, it cannot reach parent members.

How @SuperBuilder Handles Inheritance

The @SuperBuilder handler retrieves the superclass’s AST and incorporates its fields into the generated builder:

// Obtain the superclass AST
JCTree extendsClause = Javac.getExtendsClause(td);
JCExpression superclassBuilderClass = null;
if (extendsClause instanceof JCTypeApply) {
    // remember generic type arguments
    superclassTypeParams = ((JCTypeApply) extendsClause).getTypeArguments();
    extendsClause = ((JCTypeApply) extendsClause).getType();
}
if (extendsClause instanceof JCFieldAccess) {
    Name superclassName = ((JCFieldAccess) extendsClause).getIdentifier();
    String superclassBuilderClassName = superclassName.toString() + "Builder";
    superclassBuilderClass = parent.getTreeMaker().Select((JCFieldAccess) extendsClause, parent.toName(superclassBuilderClassName));
} else if (extendsClause != null) {
    String superclassBuilderClassName = extendsClause.toString() + "Builder";
    superclassBuilderClass = chainDots(parent, extendsClause.toString(), superclassBuilderClassName);
}
// ... further processing of superclass fields ...

This logic allows the generated builder to set both subclass and superclass fields, solving the limitation of the original @Builder annotation.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javacode-generationBuilder PatternSuperBuilder
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.