Default Value Initialization in Lombok: Ensuring Consistency Between Constructors and Builder Pattern

Dec 01, 2025 · Programming · 13 views · 7.8

Keywords: Lombok | Default Value Initialization | Builder Pattern

Abstract: This article explores how to ensure consistent default value initialization behavior when creating objects through constructors and the builder pattern in Java projects using the Lombok library. By analyzing the limitations of the @Builder.Default annotation and providing concrete code examples, it presents two solutions: manually implementing a no-args constructor and customizing the builder constructor, discussing their advantages in maintaining code consistency and avoiding common pitfalls.

Problem Background and Phenomenon Analysis

In Java development, the Lombok library simplifies boilerplate code through annotations, but inconsistencies may arise when handling default value initialization between constructors and the builder pattern. Consider the following UserInfo class definition:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private int id;
    private String nick;
    private boolean isEmailConfirmed = true;
}

When creating objects via the no-args constructor and builder pattern respectively:

UserInfo ui = new UserInfo();
UserInfo ui2 = UserInfo.builder().build();

System.out.println("ui: " + ui.isEmailConfirmed());
System.out.println("ui2: " + ui2.isEmailConfirmed());

The output shows inconsistency:

ui: true
ui2: false

This indicates that the builder pattern does not inherit the field's default value initialization. After adding the @Builder.Default annotation:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo { 
    private int id;
    private String nick;
    @Builder.Default
    private boolean isEmailConfirmed = true;
}

The output becomes:

ui: false
ui2: true

This reversal reveals the conflict between the @Builder.Default annotation and the no-args constructor.

Core Issue Analysis

The root cause lies in Lombok's annotation generation logic. When both @NoArgsConstructor and @Builder.Default are used, the generated no-args constructor does not include initialization code for @Builder.Default fields, while the builder pattern does. This leads to behavioral differences between the two creation methods.

From a design perspective, this inconsistency can cause several issues:

Solution 1: Manual Implementation of No-Args Constructor

The most straightforward solution is to manually implement the no-args constructor to ensure default value consistency:

@Data
@Builder
@AllArgsConstructor
public class UserInfo { 
    private int id;
    private String nick;
    @Builder.Default
    private boolean isEmailConfirmed = true;
    
    public UserInfo(){
        isEmailConfirmed = true;
    }
}

This approach ensures that the isEmailConfirmed field is initialized to true regardless of the creation method. Output verification:

ui: true
ui2: true

It's important to note that as of January 2021, Lombok has fixed related bugs in generated constructors, but similar issues may still exist when mixing @Builder.Default with explicit constructors.

Solution 2: Custom Builder Constructor

Another approach is to move the @Builder annotation from the class level to a custom constructor, handling null values via Optional:

@Data
@NoArgsConstructor
public class UserInfo {
    
    private int id;
    private String nick;
    private boolean isEmailConfirmed = true;

    @Builder
    @SuppressWarnings("unused")
    private UserInfo(int id, String nick, Boolean isEmailConfirmed) {
        this.id = id;
        this.nick = nick;
        this.isEmailConfirmed = Optional.ofNullable(isEmailConfirmed).orElse(this.isEmailConfirmed);
    }
}

Advantages of this method include:

Key consistency verification:

new UserInfo().equals(UserInfo.builder().build())

Best Practices and Recommendations

When choosing a solution, consider the following factors:

  1. Code Maintainability: Avoid duplicating initialization logic in multiple locations; centralize default value management
  2. Framework Compatibility: Ensure the solution is compatible with other frameworks used in the project (e.g., Spring, Hibernate)
  3. Team Conventions: Establish unified Lombok usage standards within the team
  4. Version Considerations: Be aware of Lombok version differences; some issues may have been fixed in specific versions

For most projects, manually implementing the no-args constructor (Solution 1) offers the best balance: simple, explicit, and not dependent on Lombok's internal implementation details. When finer control or integration with complex frameworks is needed, customizing the builder constructor (Solution 2) may be more appropriate.

Conclusion

While Lombok greatly simplifies Java development, caution is required when handling default value initialization. By understanding the limitations of the @Builder.Default annotation and adopting appropriate manual implementation strategies, consistency in object creation can be ensured. The key is recognizing the tool's limitations and reverting to traditional coding approaches when necessary to guarantee code reliability and maintainability.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.