Comprehensive builder pattern implementation with support for inheritance, defaults, collection handling, and extensive customization options.
Generates a complete builder pattern implementation with fluent method chaining and customizable generation options.
/**
* The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class
* that contains a member which is annotated with @Builder.
*
* If a member is annotated, it must be either a constructor or a method. If a class is annotated,
* then a package-private constructor is generated with all fields as arguments, and it is as if this
* constructor has been annotated with @Builder instead.
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
/**
* @return Name of the method that creates a new builder instance. Default: "builder".
* If the empty string, suppress generating the builder method.
*/
String builderMethodName() default "builder";
/**
* @return Name of the method in the builder class that creates an instance of your @Builder-annotated class.
*/
String buildMethodName() default "build";
/**
* Name of the builder class.
*
* Default for @Builder on types and constructors: (TypeName)Builder.
* Default for @Builder on methods: (ReturnTypeName)Builder.
*
* @return Name of the builder class that will be generated.
*/
String builderClassName() default "";
/**
* If true, generate an instance method to obtain a builder that is initialized with the values of this instance.
* Legal only if @Builder is used on a constructor, on the type itself, or on a static method that returns
* an instance of the declaring type.
*
* @return Whether to generate a toBuilder() method.
*/
boolean toBuilder() default false;
/**
* Sets the access level of the generated builder class. By default, generated builder classes are public.
* Note: This does nothing if you write your own builder class (we won't change its access level).
*
* @return The builder class will be generated with this access modifier.
*/
AccessLevel access() default AccessLevel.PUBLIC;
/**
* Prefix to prepend to 'set' methods in the generated builder class. By default, generated methods do not include a prefix.
*
* For example, a method normally generated as someField(String someField) would instead be
* generated as withSomeField(String someField) if using @Builder(setterPrefix = "with").
*
* @return The prefix to prepend to generated method names.
*/
String setterPrefix() default "";
}Usage Examples:
import lombok.Builder;
@Builder
public class User {
private String name;
private int age;
private String email;
private boolean active;
}
// Generated Builder class and methods:
// public static UserBuilder builder() { return new UserBuilder(); }
// public static class UserBuilder {
// private String name;
// private int age;
// private String email;
// private boolean active;
//
// public UserBuilder name(String name) { this.name = name; return this; }
// public UserBuilder age(int age) { this.age = age; return this; }
// public UserBuilder email(String email) { this.email = email; return this; }
// public UserBuilder active(boolean active) { this.active = active; return this; }
// public User build() { return new User(name, age, email, active); }
// }
// Usage
User user = User.builder()
.name("John Doe")
.age(30)
.email("john@example.com")
.active(true)
.build();With Custom Configuration:
@Builder(
builderMethodName = "create",
buildMethodName = "construct",
setterPrefix = "with"
)
public class Product {
private String name;
private double price;
}
// Usage
Product product = Product.create()
.withName("Laptop")
.withPrice(999.99)
.construct();Specifies default values for builder fields that are used when the field is not explicitly set.
/**
* The field annotated with @Default must have an initializing expression;
* that expression is taken as the default to be used if not explicitly set during building.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Default {
}Usage Examples:
import lombok.Builder;
@Builder
public class Configuration {
private String host;
@Builder.Default
private int port = 8080;
@Builder.Default
private boolean ssl = false;
@Builder.Default
private List<String> allowedHosts = new ArrayList<>();
}
// Usage
Configuration config = Configuration.builder()
.host("localhost")
// port will be 8080, ssl will be false, allowedHosts will be empty ArrayList
.build();Used with @Builder to generate methods for adding individual items to collections, maps, and arrays.
/**
* Used with @Builder to generate singular add methods for collections.
* The generated builder will have methods to add individual items to the collection,
* as well as methods to add all items from another collection.
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface Singular {
/**
* @return The singular name to use for the generated methods.
* If not specified, lombok will attempt to singularize the field name.
*/
String value() default "";
/**
* @return Whether to ignore null collections when calling the bulk methods.
*/
boolean ignoreNullCollections() default false;
}Usage Examples:
import lombok.Builder;
import lombok.Singular;
import java.util.List;
import java.util.Set;
import java.util.Map;
@Builder
public class Team {
@Singular
private List<String> members;
@Singular("skill")
private Set<String> skills;
@Singular
private Map<String, Integer> scores;
}
// Generated methods for members:
// public TeamBuilder member(String member) { /* add single member */ }
// public TeamBuilder members(Collection<? extends String> members) { /* add all members */ }
// public TeamBuilder clearMembers() { /* clear all members */ }
// Generated methods for skills:
// public TeamBuilder skill(String skill) { /* add single skill */ }
// public TeamBuilder skills(Collection<? extends String> skills) { /* add all skills */ }
// public TeamBuilder clearSkills() { /* clear all skills */ }
// Generated methods for scores:
// public TeamBuilder score(String key, Integer value) { /* add single score */ }
// public TeamBuilder scores(Map<? extends String, ? extends Integer> scores) { /* add all scores */ }
// public TeamBuilder clearScores() { /* clear all scores */ }
// Usage
Team team = Team.builder()
.member("Alice")
.member("Bob")
.skill("Java")
.skill("Python")
.score("Alice", 95)
.score("Bob", 87)
.build();Specifies how to obtain values for toBuilder() functionality when the field name or access pattern differs from the default.
/**
* Put on a field (in case of @Builder on a type) or a parameter (for @Builder on a constructor or static method) to
* indicate how lombok should obtain a value for this field or parameter given an instance; this is only relevant if toBuilder is true.
*
* You do not need to supply an @ObtainVia annotation unless you wish to change the default behaviour: Use a field with the same name.
*
* Note that one of field or method should be set, or an error is generated.
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface ObtainVia {
/**
* @return Tells lombok to obtain a value with the expression this.value.
*/
String field() default "";
/**
* @return Tells lombok to obtain a value with the expression this.method().
*/
String method() default "";
/**
* @return Tells lombok to obtain a value with the expression SelfType.method(this); requires method to be set.
*/
boolean isStatic() default false;
}Usage Examples:
@Builder(toBuilder = true)
public class Person {
private String firstName;
private String lastName;
@Builder.ObtainVia(method = "getFullName")
private String fullName;
@Builder.ObtainVia(field = "internalAge")
private int age;
private int internalAge;
public String getFullName() {
return firstName + " " + lastName;
}
}
// toBuilder() will use getFullName() method and internalAge field
Person person = new Person("John", "Doe", "John Doe", 30);
Person modified = person.toBuilder().age(31).build();Apply @Builder to static factory methods:
public class Range {
private final int start;
private final int end;
private Range(int start, int end) {
this.start = start;
this.end = end;
}
@Builder(builderMethodName = "range")
public static Range create(int start, int end) {
return new Range(start, end);
}
}
// Usage
Range range = Range.range()
.start(1)
.end(10)
.build();Apply @Builder to specific constructors:
public class Employee {
private final String name;
private final String department;
private final double salary;
@Builder
public Employee(String name, String department) {
this.name = name;
this.department = department;
this.salary = 0.0;
}
// Other constructors without @Builder
public Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
}@Builder
public class Vehicle {
private String make;
private String model;
private int year;
}
// For inheritance, consider using @SuperBuilder insteadGenerate builders from existing instances:
@Builder(toBuilder = true)
public class Settings {
private String theme;
private boolean darkMode;
private int fontSize;
}
// Usage
Settings original = Settings.builder()
.theme("default")
.darkMode(false)
.fontSize(12)
.build();
Settings modified = original.toBuilder()
.darkMode(true)
.fontSize(14)
.build();@Builder
public class User {
private String email;
private int age;
// Custom builder class to add validation
public static class UserBuilder {
public User build() {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
return new User(email, age);
}
}
}@Builder
public class Report {
@Singular("entry")
private Map<String, List<String>> entries;
@Singular
private List<Tag> tags;
}
// Usage with complex collections
Report report = Report.builder()
.entry("errors", Arrays.asList("Error 1", "Error 2"))
.entry("warnings", Arrays.asList("Warning 1"))
.tag(new Tag("important"))
.tag(new Tag("reviewed"))
.build();Configures Jackson JSON serialization to work seamlessly with Lombok builders.
/**
* The @Jacksonized annotation is an add-on annotation for @Builder, @SuperBuilder, and @Accessors.
*
* For @Builder and @SuperBuilder, it automatically configures the generated builder class to be used by Jackson's
* deserialization. It only has an effect if present at a context where there is also a @Builder or a @SuperBuilder.
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface Jacksonized {
}Usage Examples:
import lombok.Builder;
import lombok.extern.jackson.Jacksonized;
import com.fasterxml.jackson.databind.ObjectMapper;
@Builder
@Jacksonized
public class ApiResponse {
private String status;
private String message;
private Object data;
}
// Jackson can now deserialize JSON directly to builder
ObjectMapper mapper = new ObjectMapper();
String json = """
{
"status": "success",
"message": "Request processed",
"data": {"id": 123}
}
""";
ApiResponse response = mapper.readValue(json, ApiResponse.class);With @SuperBuilder:
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Jacksonized
public class BaseEntity {
private String id;
private long timestamp;
}
@SuperBuilder
@Jacksonized
public class User extends BaseEntity {
private String name;
private String email;
}
// Jackson can deserialize to inherited builder classes
String userJson = """
{
"id": "user123",
"timestamp": 1234567890,
"name": "John Doe",
"email": "john@example.com"
}
""";
User user = mapper.readValue(userJson, User.class);What @Jacksonized Does:
@JsonDeserialize(builder=...)@JsonPOJOBuilder(withPrefix="") on the generated builder classsetterPrefix and buildMethodName configurations@SuperBuilder, makes the builder implementation class package-privateBuilder Method Prefix Configuration:
@Builder(setterPrefix = "with")
@Jacksonized
public class ConfiguredBuilder {
private String name;
private int value;
}
// Generated builder methods: withName(), withValue()
// Jackson is automatically configured to recognize the "with" prefix