0
# Builder Pattern
1
2
Comprehensive builder pattern implementation with support for inheritance, defaults, collection handling, and extensive customization options.
3
4
## Capabilities
5
6
### @Builder Annotation
7
8
Generates a complete builder pattern implementation with fluent method chaining and customizable generation options.
9
10
```java { .api }
11
/**
12
* The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class
13
* that contains a member which is annotated with @Builder.
14
*
15
* If a member is annotated, it must be either a constructor or a method. If a class is annotated,
16
* then a package-private constructor is generated with all fields as arguments, and it is as if this
17
* constructor has been annotated with @Builder instead.
18
*/
19
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
20
@Retention(RetentionPolicy.SOURCE)
21
public @interface Builder {
22
/**
23
* @return Name of the method that creates a new builder instance. Default: "builder".
24
* If the empty string, suppress generating the builder method.
25
*/
26
String builderMethodName() default "builder";
27
28
/**
29
* @return Name of the method in the builder class that creates an instance of your @Builder-annotated class.
30
*/
31
String buildMethodName() default "build";
32
33
/**
34
* Name of the builder class.
35
*
36
* Default for @Builder on types and constructors: (TypeName)Builder.
37
* Default for @Builder on methods: (ReturnTypeName)Builder.
38
*
39
* @return Name of the builder class that will be generated.
40
*/
41
String builderClassName() default "";
42
43
/**
44
* If true, generate an instance method to obtain a builder that is initialized with the values of this instance.
45
* Legal only if @Builder is used on a constructor, on the type itself, or on a static method that returns
46
* an instance of the declaring type.
47
*
48
* @return Whether to generate a toBuilder() method.
49
*/
50
boolean toBuilder() default false;
51
52
/**
53
* Sets the access level of the generated builder class. By default, generated builder classes are public.
54
* Note: This does nothing if you write your own builder class (we won't change its access level).
55
*
56
* @return The builder class will be generated with this access modifier.
57
*/
58
AccessLevel access() default AccessLevel.PUBLIC;
59
60
/**
61
* Prefix to prepend to 'set' methods in the generated builder class. By default, generated methods do not include a prefix.
62
*
63
* For example, a method normally generated as someField(String someField) would instead be
64
* generated as withSomeField(String someField) if using @Builder(setterPrefix = "with").
65
*
66
* @return The prefix to prepend to generated method names.
67
*/
68
String setterPrefix() default "";
69
}
70
```
71
72
**Usage Examples:**
73
74
```java
75
import lombok.Builder;
76
77
@Builder
78
public class User {
79
private String name;
80
private int age;
81
private String email;
82
private boolean active;
83
}
84
85
// Generated Builder class and methods:
86
// public static UserBuilder builder() { return new UserBuilder(); }
87
// public static class UserBuilder {
88
// private String name;
89
// private int age;
90
// private String email;
91
// private boolean active;
92
//
93
// public UserBuilder name(String name) { this.name = name; return this; }
94
// public UserBuilder age(int age) { this.age = age; return this; }
95
// public UserBuilder email(String email) { this.email = email; return this; }
96
// public UserBuilder active(boolean active) { this.active = active; return this; }
97
// public User build() { return new User(name, age, email, active); }
98
// }
99
100
// Usage
101
User user = User.builder()
102
.name("John Doe")
103
.age(30)
104
.email("john@example.com")
105
.active(true)
106
.build();
107
```
108
109
**With Custom Configuration:**
110
111
```java
112
@Builder(
113
builderMethodName = "create",
114
buildMethodName = "construct",
115
setterPrefix = "with"
116
)
117
public class Product {
118
private String name;
119
private double price;
120
}
121
122
// Usage
123
Product product = Product.create()
124
.withName("Laptop")
125
.withPrice(999.99)
126
.construct();
127
```
128
129
### @Builder.Default Annotation
130
131
Specifies default values for builder fields that are used when the field is not explicitly set.
132
133
```java { .api }
134
/**
135
* The field annotated with @Default must have an initializing expression;
136
* that expression is taken as the default to be used if not explicitly set during building.
137
*/
138
@Target(ElementType.FIELD)
139
@Retention(RetentionPolicy.SOURCE)
140
public @interface Default {
141
}
142
```
143
144
**Usage Examples:**
145
146
```java
147
import lombok.Builder;
148
149
@Builder
150
public class Configuration {
151
private String host;
152
153
@Builder.Default
154
private int port = 8080;
155
156
@Builder.Default
157
private boolean ssl = false;
158
159
@Builder.Default
160
private List<String> allowedHosts = new ArrayList<>();
161
}
162
163
// Usage
164
Configuration config = Configuration.builder()
165
.host("localhost")
166
// port will be 8080, ssl will be false, allowedHosts will be empty ArrayList
167
.build();
168
```
169
170
### @Singular Annotation
171
172
Used with @Builder to generate methods for adding individual items to collections, maps, and arrays.
173
174
```java { .api }
175
/**
176
* Used with @Builder to generate singular add methods for collections.
177
* The generated builder will have methods to add individual items to the collection,
178
* as well as methods to add all items from another collection.
179
*/
180
@Target({ElementType.FIELD, ElementType.PARAMETER})
181
@Retention(RetentionPolicy.SOURCE)
182
public @interface Singular {
183
/**
184
* @return The singular name to use for the generated methods.
185
* If not specified, lombok will attempt to singularize the field name.
186
*/
187
String value() default "";
188
189
/**
190
* @return Whether to ignore null collections when calling the bulk methods.
191
*/
192
boolean ignoreNullCollections() default false;
193
}
194
```
195
196
**Usage Examples:**
197
198
```java
199
import lombok.Builder;
200
import lombok.Singular;
201
import java.util.List;
202
import java.util.Set;
203
import java.util.Map;
204
205
@Builder
206
public class Team {
207
@Singular
208
private List<String> members;
209
210
@Singular("skill")
211
private Set<String> skills;
212
213
@Singular
214
private Map<String, Integer> scores;
215
}
216
217
// Generated methods for members:
218
// public TeamBuilder member(String member) { /* add single member */ }
219
// public TeamBuilder members(Collection<? extends String> members) { /* add all members */ }
220
// public TeamBuilder clearMembers() { /* clear all members */ }
221
222
// Generated methods for skills:
223
// public TeamBuilder skill(String skill) { /* add single skill */ }
224
// public TeamBuilder skills(Collection<? extends String> skills) { /* add all skills */ }
225
// public TeamBuilder clearSkills() { /* clear all skills */ }
226
227
// Generated methods for scores:
228
// public TeamBuilder score(String key, Integer value) { /* add single score */ }
229
// public TeamBuilder scores(Map<? extends String, ? extends Integer> scores) { /* add all scores */ }
230
// public TeamBuilder clearScores() { /* clear all scores */ }
231
232
// Usage
233
Team team = Team.builder()
234
.member("Alice")
235
.member("Bob")
236
.skill("Java")
237
.skill("Python")
238
.score("Alice", 95)
239
.score("Bob", 87)
240
.build();
241
```
242
243
### @Builder.ObtainVia Annotation
244
245
Specifies how to obtain values for toBuilder() functionality when the field name or access pattern differs from the default.
246
247
```java { .api }
248
/**
249
* Put on a field (in case of @Builder on a type) or a parameter (for @Builder on a constructor or static method) to
250
* indicate how lombok should obtain a value for this field or parameter given an instance; this is only relevant if toBuilder is true.
251
*
252
* You do not need to supply an @ObtainVia annotation unless you wish to change the default behaviour: Use a field with the same name.
253
*
254
* Note that one of field or method should be set, or an error is generated.
255
*/
256
@Target({ElementType.FIELD, ElementType.PARAMETER})
257
@Retention(RetentionPolicy.SOURCE)
258
public @interface ObtainVia {
259
/**
260
* @return Tells lombok to obtain a value with the expression this.value.
261
*/
262
String field() default "";
263
264
/**
265
* @return Tells lombok to obtain a value with the expression this.method().
266
*/
267
String method() default "";
268
269
/**
270
* @return Tells lombok to obtain a value with the expression SelfType.method(this); requires method to be set.
271
*/
272
boolean isStatic() default false;
273
}
274
```
275
276
**Usage Examples:**
277
278
```java
279
@Builder(toBuilder = true)
280
public class Person {
281
private String firstName;
282
private String lastName;
283
284
@Builder.ObtainVia(method = "getFullName")
285
private String fullName;
286
287
@Builder.ObtainVia(field = "internalAge")
288
private int age;
289
290
private int internalAge;
291
292
public String getFullName() {
293
return firstName + " " + lastName;
294
}
295
}
296
297
// toBuilder() will use getFullName() method and internalAge field
298
Person person = new Person("John", "Doe", "John Doe", 30);
299
Person modified = person.toBuilder().age(31).build();
300
```
301
302
## Advanced Builder Patterns
303
304
### Builder on Methods
305
306
Apply @Builder to static factory methods:
307
308
```java
309
public class Range {
310
private final int start;
311
private final int end;
312
313
private Range(int start, int end) {
314
this.start = start;
315
this.end = end;
316
}
317
318
@Builder(builderMethodName = "range")
319
public static Range create(int start, int end) {
320
return new Range(start, end);
321
}
322
}
323
324
// Usage
325
Range range = Range.range()
326
.start(1)
327
.end(10)
328
.build();
329
```
330
331
### Builder on Constructors
332
333
Apply @Builder to specific constructors:
334
335
```java
336
public class Employee {
337
private final String name;
338
private final String department;
339
private final double salary;
340
341
@Builder
342
public Employee(String name, String department) {
343
this.name = name;
344
this.department = department;
345
this.salary = 0.0;
346
}
347
348
// Other constructors without @Builder
349
public Employee(String name, String department, double salary) {
350
this.name = name;
351
this.department = department;
352
this.salary = salary;
353
}
354
}
355
```
356
357
### Builder with Inheritance
358
359
```java
360
@Builder
361
public class Vehicle {
362
private String make;
363
private String model;
364
private int year;
365
}
366
367
// For inheritance, consider using @SuperBuilder instead
368
```
369
370
### ToBuilder Functionality
371
372
Generate builders from existing instances:
373
374
```java
375
@Builder(toBuilder = true)
376
public class Settings {
377
private String theme;
378
private boolean darkMode;
379
private int fontSize;
380
}
381
382
// Usage
383
Settings original = Settings.builder()
384
.theme("default")
385
.darkMode(false)
386
.fontSize(12)
387
.build();
388
389
Settings modified = original.toBuilder()
390
.darkMode(true)
391
.fontSize(14)
392
.build();
393
```
394
395
## Builder Best Practices
396
397
### Validation in Builder
398
399
```java
400
@Builder
401
public class User {
402
private String email;
403
private int age;
404
405
// Custom builder class to add validation
406
public static class UserBuilder {
407
public User build() {
408
if (email == null || !email.contains("@")) {
409
throw new IllegalArgumentException("Invalid email");
410
}
411
if (age < 0) {
412
throw new IllegalArgumentException("Age cannot be negative");
413
}
414
return new User(email, age);
415
}
416
}
417
}
418
```
419
420
### Complex Collection Handling
421
422
```java
423
@Builder
424
public class Report {
425
@Singular("entry")
426
private Map<String, List<String>> entries;
427
428
@Singular
429
private List<Tag> tags;
430
}
431
432
// Usage with complex collections
433
Report report = Report.builder()
434
.entry("errors", Arrays.asList("Error 1", "Error 2"))
435
.entry("warnings", Arrays.asList("Warning 1"))
436
.tag(new Tag("important"))
437
.tag(new Tag("reviewed"))
438
.build();
439
```
440
441
### @Jacksonized Integration
442
443
Configures Jackson JSON serialization to work seamlessly with Lombok builders.
444
445
```java { .api }
446
/**
447
* The @Jacksonized annotation is an add-on annotation for @Builder, @SuperBuilder, and @Accessors.
448
*
449
* For @Builder and @SuperBuilder, it automatically configures the generated builder class to be used by Jackson's
450
* deserialization. It only has an effect if present at a context where there is also a @Builder or a @SuperBuilder.
451
*/
452
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
453
@Retention(RetentionPolicy.SOURCE)
454
public @interface Jacksonized {
455
}
456
```
457
458
**Usage Examples:**
459
460
```java
461
import lombok.Builder;
462
import lombok.extern.jackson.Jacksonized;
463
import com.fasterxml.jackson.databind.ObjectMapper;
464
465
@Builder
466
@Jacksonized
467
public class ApiResponse {
468
private String status;
469
private String message;
470
private Object data;
471
}
472
473
// Jackson can now deserialize JSON directly to builder
474
ObjectMapper mapper = new ObjectMapper();
475
String json = """
476
{
477
"status": "success",
478
"message": "Request processed",
479
"data": {"id": 123}
480
}
481
""";
482
483
ApiResponse response = mapper.readValue(json, ApiResponse.class);
484
```
485
486
**With @SuperBuilder:**
487
488
```java
489
import lombok.experimental.SuperBuilder;
490
import lombok.extern.jackson.Jacksonized;
491
492
@SuperBuilder
493
@Jacksonized
494
public class BaseEntity {
495
private String id;
496
private long timestamp;
497
}
498
499
@SuperBuilder
500
@Jacksonized
501
public class User extends BaseEntity {
502
private String name;
503
private String email;
504
}
505
506
// Jackson can deserialize to inherited builder classes
507
String userJson = """
508
{
509
"id": "user123",
510
"timestamp": 1234567890,
511
"name": "John Doe",
512
"email": "john@example.com"
513
}
514
""";
515
516
User user = mapper.readValue(userJson, User.class);
517
```
518
519
**What @Jacksonized Does:**
520
521
1. Configures Jackson to use the builder for deserialization with `@JsonDeserialize(builder=...)`
522
2. Copies Jackson configuration annotations from the class to the builder class
523
3. Inserts `@JsonPOJOBuilder(withPrefix="")` on the generated builder class
524
4. Respects custom `setterPrefix` and `buildMethodName` configurations
525
5. For `@SuperBuilder`, makes the builder implementation class package-private
526
527
**Builder Method Prefix Configuration:**
528
529
```java
530
@Builder(setterPrefix = "with")
531
@Jacksonized
532
public class ConfiguredBuilder {
533
private String name;
534
private int value;
535
}
536
537
// Generated builder methods: withName(), withValue()
538
// Jackson is automatically configured to recognize the "with" prefix
539
```