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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
