Java RecordBuilder helps generate builder classes for Java records, making object creation easier and cleaner. In this article, let us delve into understanding how Java RecordBuilder works through a practical employee management example.
1. What is a RecordBuilder in Java?
The RecordBuilder is a Java library designed to automatically generate builder classes for Java Records. Introduced in Java 14 (as a preview) and standardized in Java 16, records are immutable data carriers that reduce boilerplate code. However, they lack a built-in builder pattern, which can be essential when constructing complex objects with many optional parameters or nested structures.
1.1 Why use RecordBuilder?
By using RecordBuilder, developers can:
- Automatically generate a type-safe, fluent builder for a given record.
- Set default values for optional fields without cluttering the constructor.
- Leverage compile-time type checks to avoid runtime errors.
- Maintain immutability while providing a flexible object creation process.
This approach is especially useful when working with records that have multiple fields, nested components, or require validation before construction.
2. Scenario
To understand how RecordBuilder works in practice, we will create a small Employee Management example using Java records and the RecordBuilder library. The example will demonstrate the advantages of this approach when working with immutable data structures in a real-world scenario. Our Employee record will feature:
- Basic employee details – such as
id,name, anddesignation. - Nested record for address – containing
street,city, andzipCode. - Default values for optional fields – for example, if no department is provided, default to “General”.
- Immutable list of skills – ensuring that the skill set cannot be altered after creation.
- Validation logic in the constructor – such as checking that
idis positive andnameis not blank.
3. Code Example
In this section, we will walk through a complete example of using RecordBuilder to create immutable Java records with a fluent builder API. The example will cover:
3.1 Maven Dependency (pom.xml)
To use RecordBuilder, you need to include its dependency in your Maven project. The provided scope ensures the library is available at compile-time for annotation processing, but is not bundled into the final artifact since it is not required at runtime.
<dependency> <groupId>io.soabase.record-builder</groupId> <artifactId>record-builder-core</artifactId> <version>your_latest_jar_version</version> <scope>provided</scope> </dependency>
3.2 Code Example
In this section, we will implement the employee management scenario using Java records and the RecordBuilder library. The example will showcase how to define immutable data structures, apply validation rules, set default values for optional fields, and work with nested records. We will then use the generated builder classes to construct objects in a clean, readable, and type-safe way.
import io.soabase.recordbuilder.core.RecordBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
// Customization and Options
@RecordBuilder.Options(addStaticBuilder = true)
@RecordBuilder
public record Employee(
int id,
String name,
String designation,
Address address,
String department,
List skills
) {
public Employee {
// Validation
if (id <= 0) {
throw new IllegalArgumentException("Employee ID must be positive");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
if (designation == null || designation.isBlank()) {
throw new IllegalArgumentException("Designation cannot be empty");
}
if (address == null) {
throw new IllegalArgumentException("Address cannot be null");
}
// Default values
if (department == null || department.isBlank()) {
department = "General";
}
// Ensure skills list is immutable and non-null
skills = (skills == null) ? List.of() : List.copyOf(skills);
}
}
@RecordBuilder.Options(addStaticBuilder = true)
@RecordBuilder
public record Address(
String street,
String city,
String zipCode
) {
public Address {
if (street == null || street.isBlank()) {
throw new IllegalArgumentException("Street cannot be empty");
}
if (city == null || city.isBlank()) {
throw new IllegalArgumentException("City cannot be empty");
}
if (zipCode == null || zipCode.isBlank()) {
throw new IllegalArgumentException("Zip code cannot be empty");
}
}
}
public class Main {
public static void main(String[] args) {
// Creating Address using generated builder
Address addr1 = Address.builder()
.street("123 Main St")
.city("Springfield")
.zipCode("12345")
.build();
Address addr2 = Address.builder()
.street("45 Park Avenue")
.city("Metropolis")
.zipCode("54321")
.build();
// Employee with default department and empty skills
Employee emp1 = Employee.builder()
.id(101)
.name("Alice Johnson")
.designation("Software Engineer")
.address(addr1)
.build();
// Employee with full details
Employee emp2 = Employee.builder()
.id(102)
.name("Bob Smith")
.designation("Project Manager")
.address(addr2)
.department("Management")
.skills(List.of("Leadership", "Agile", "Communication"))
.build();
System.out.println(emp1);
System.out.println(emp2);
// Uncommenting the below will trigger validation errors
// Employee invalid = Employee.builder()
// .id(-1)
// .name("")
// .designation("DevOps Engineer")
// .address(addr1)
// .build();
}
}
3.2.1 Code Explanation
The code demonstrates how to use the RecordBuilder library in Java to create immutable records with a fluent builder API, following the employee management scenario described earlier. We define two records – Employee and Address – each annotated with @RecordBuilder and configured with @RecordBuilder.Options(addStaticBuilder = true) so that static builder methods are generated. The Employee record includes fields for basic employee details, a nested Address record, an optional department with a default value of “General”, and an immutable list of skills. In the compact constructor of Employee, we enforce validation rules such as requiring a positive id, non-empty name and designation, and a non-null address, while defaulting department if not provided and ensuring the skills list is immutable via List.copyOf(). The Address record similarly validates its street, city, and zipCode fields. In the Main class, we use the generated builder() methods to construct Address and Employee instances, showing both a minimal creation with defaults and a fully populated example. Attempting to build an object with invalid data (such as a negative ID or blank name) would trigger the defined validation checks at runtime, ensuring that all created records are valid, consistent, and immutable.
3.2.2 Code Output
Upon execution, the program produces the following output:
Employee[id=101, name=Alice Johnson, designation=Software Engineer, address=Address[street=123 Main St, city=Springfield, zipCode=12345], department=General, skills=[]] Employee[id=102, name=Bob Smith, designation=Project Manager, address=Address[street=45 Park Avenue, city=Metropolis, zipCode=54321], department=Management, skills=[Leadership, Agile, Communication]]
If you uncomment the invalid employee creation block, the program will instead throw:
Exception in thread "main" java.lang.IllegalArgumentException: Employee ID must be positive at Employee.(Employee.java:...) at EmployeeBuilder.build(EmployeeBuilder.java:...) ...
4. Comparison: RecordBuilder vs Manual Builder
The table below compares RecordBuilder with a manually written builder pattern, highlighting differences in code complexity, safety, and ease of use. RecordBuilder significantly reduces boilerplate while still offering flexibility through custom logic inside record constructors.
| Feature | RecordBuilder | Manual Builder |
|---|---|---|
| Boilerplate Code | Generates the entire builder automatically during compilation using annotations, so you only define the record and optionally add logic in the constructor. No need to manually write builder classes, setter methods, or the build() method. | Requires creating a separate builder class by hand, including private fields, setters for each field, a build() method, and often repetitive null-check or default-handling code. |
| Compile-time Safety | Generated builder methods are type-safe and linked directly to the record’s fields. If you change the record signature, the builder updates automatically at compile time, preventing runtime mismatches. | Can also be type-safe, but changes to the target class require manual synchronization in the builder class. This increases the risk of human error and compile-time warnings. |
| Custom Logic | Business rules, validations, and default values can be implemented inside the record’s canonical constructor. These apply to all instances, whether built manually or via the generated builder. | You need to implement validation and default assignment logic manually in the builder or target class, often leading to duplicated code and maintenance overhead. |
| Ease of Use | High — supports fluent API with method chaining, static builder methods, and optional field handling. Minimal effort for developers; builder is always in sync with the record. | Medium — while fluent API can be achieved, it requires more upfront coding effort. Any changes to the target class require manual updates to the builder, increasing maintenance. |
5. Conclusion
RecordBuilder extends Java 16’s records with a fluent, type-safe builder pattern that eliminates boilerplate and preserves immutability. This single example demonstrates how defaults, validation, and a clean API can be combined in a production-ready setup.
Thank you!
We will contact you soon.
Yatin BatraAugust 13th, 2025Last Updated: August 13th, 2025

This site uses Akismet to reduce spam. Learn how your comment data is processed.