0
# Builder Pattern
1
2
Fluent builder pattern implementation with support for inheritance, default values, collection handling, and Jackson integration. The builder pattern provides a clean, readable way to construct objects with many optional parameters.
3
4
## Capabilities
5
6
### @Builder Annotation
7
8
Generates a comprehensive builder pattern implementation with fluent method chaining and customizable naming conventions.
9
10
```java { .api }
11
/**
12
* Creates a builder pattern implementation for the annotated class, constructor, or method.
13
* Generates an inner builder class with fluent setter methods and a build() method.
14
*/
15
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
16
@interface Builder {
17
/**
18
* Name of the static method that creates a new builder instance
19
* @return Method name (default: "builder"). Empty string suppresses generation
20
*/
21
String builderMethodName() default "builder";
22
23
/**
24
* Name of the method in the builder class that creates the final instance
25
* @return Method name for building the instance (default: "build")
26
*/
27
String buildMethodName() default "build";
28
29
/**
30
* Name of the generated builder class
31
* @return Builder class name. Defaults to (TypeName)Builder or (ReturnTypeName)Builder
32
*/
33
String builderClassName() default "";
34
35
/**
36
* Generate a toBuilder() instance method that creates a pre-populated builder
37
* @return Whether to generate toBuilder() method (default: false)
38
*/
39
boolean toBuilder() default false;
40
41
/**
42
* Access level for the generated builder class
43
* @return Access level for the builder class (default: PUBLIC)
44
*/
45
AccessLevel access() default AccessLevel.PUBLIC;
46
47
/**
48
* Prefix for builder setter methods
49
* @return Prefix to prepend to setter method names (default: empty)
50
*/
51
String setterPrefix() default "";
52
}
53
54
/**
55
* Marks a field for default value in builder pattern
56
* Field must have an initializing expression
57
*/
58
@Target(ElementType.FIELD)
59
@interface Builder.Default {}
60
61
/**
62
* Specifies how to obtain field value for toBuilder functionality
63
*/
64
@Target({ElementType.FIELD, ElementType.PARAMETER})
65
@interface Builder.ObtainVia {
66
/**
67
* Field name to use for obtaining value
68
* @return Field name for this.fieldName access
69
*/
70
String field() default "";
71
72
/**
73
* Method name to use for obtaining value
74
* @return Method name for this.methodName() access
75
*/
76
String method() default "";
77
78
/**
79
* Whether the method is static
80
* @return True for ClassName.method(this) access pattern
81
*/
82
boolean isStatic() default false;
83
}
84
```
85
86
**Usage Examples:**
87
88
```java
89
import lombok.Builder;
90
import lombok.Getter;
91
import lombok.ToString;
92
93
@Builder
94
@Getter
95
@ToString
96
public class Person {
97
private String firstName;
98
private String lastName;
99
private int age;
100
private String email;
101
private String phone;
102
}
103
104
// Generated builder class and usage:
105
Person person = Person.builder()
106
.firstName("John")
107
.lastName("Doe")
108
.age(30)
109
.email("john.doe@example.com")
110
.phone("555-1234")
111
.build();
112
```
113
114
Builder with default values:
115
```java
116
@Builder
117
@Getter
118
public class Configuration {
119
private String host;
120
121
@Builder.Default
122
private int port = 8080;
123
124
@Builder.Default
125
private boolean ssl = false;
126
127
@Builder.Default
128
private long timeout = 30000L;
129
}
130
131
// Usage with defaults:
132
Configuration config1 = Configuration.builder()
133
.host("localhost")
134
.build(); // Uses default port=8080, ssl=false, timeout=30000L
135
136
Configuration config2 = Configuration.builder()
137
.host("example.com")
138
.port(443)
139
.ssl(true)
140
.build();
141
```
142
143
Builder with toBuilder support:
144
```java
145
@Builder(toBuilder = true)
146
@Getter
147
public class ApiRequest {
148
private String endpoint;
149
private String method;
150
private Map<String, String> headers;
151
private String body;
152
}
153
154
// Usage with toBuilder:
155
ApiRequest baseRequest = ApiRequest.builder()
156
.endpoint("/api/v1/users")
157
.method("GET")
158
.headers(Map.of("Accept", "application/json"))
159
.build();
160
161
// Create modified version:
162
ApiRequest postRequest = baseRequest.toBuilder()
163
.method("POST")
164
.body("{\"name\": \"John\"}")
165
.build();
166
```
167
168
Custom builder configuration:
169
```java
170
@Builder(
171
builderMethodName = "newBuilder",
172
buildMethodName = "create",
173
builderClassName = "PersonFactory",
174
setterPrefix = "with",
175
access = AccessLevel.PACKAGE
176
)
177
public class CustomPerson {
178
private String name;
179
private int age;
180
}
181
182
// Usage with custom configuration:
183
CustomPerson person = CustomPerson.newBuilder()
184
.withName("Alice")
185
.withAge(25)
186
.create();
187
```
188
189
### @Singular Annotation
190
191
Used with @Builder to generate special methods for collection fields, providing both single-item and batch addition capabilities.
192
193
```java { .api }
194
/**
195
* Applied to collection fields in @Builder classes to generate singular addition methods.
196
* Creates methods for adding single items, multiple items, and clearing the collection.
197
*/
198
@Target({ElementType.FIELD, ElementType.PARAMETER})
199
@interface Singular {
200
/**
201
* Singular name for the field. If not specified, lombok attempts to determine
202
* the singular form automatically from the field name.
203
* @return Singular form of the field name
204
*/
205
String value() default "";
206
207
/**
208
* How to handle null collections in batch addition methods
209
* @return If true, null collections are treated as empty (default: false)
210
*/
211
boolean ignoreNullCollections() default false;
212
}
213
```
214
215
**Usage Examples:**
216
217
```java
218
import lombok.Builder;
219
import lombok.Singular;
220
import lombok.Getter;
221
import java.util.*;
222
223
@Builder
224
@Getter
225
public class Team {
226
private String name;
227
228
@Singular
229
private List<String> members;
230
231
@Singular("skill")
232
private Set<String> skills;
233
234
@Singular
235
private Map<String, String> properties;
236
}
237
238
// Generated methods for collections:
239
Team team = Team.builder()
240
.name("Development Team")
241
242
// Single member additions
243
.member("Alice")
244
.member("Bob")
245
246
// Batch member addition
247
.members(Arrays.asList("Charlie", "David"))
248
249
// Single skill additions
250
.skill("Java")
251
.skill("Python")
252
253
// Batch skill addition
254
.skills(Set.of("Docker", "Kubernetes"))
255
256
// Single property additions
257
.property("department", "Engineering")
258
.property("location", "Remote")
259
260
// Batch property addition
261
.properties(Map.of("budget", "100k", "duration", "6 months"))
262
263
.build();
264
265
// Clear methods are also generated:
266
Team emptyTeam = Team.builder()
267
.name("New Team")
268
.member("Alice")
269
.clearMembers() // Removes all added members
270
.member("Bob") // Team will only have Bob
271
.build();
272
```
273
274
Singular with custom collection types:
275
```java
276
@Builder
277
@Getter
278
public class Library {
279
@Singular
280
private List<Book> books;
281
282
@Singular("author")
283
private Set<String> authors;
284
285
@Singular
286
private Map<String, Category> categories;
287
288
// Guava collections support
289
@Singular
290
private ImmutableList<String> tags;
291
292
@Singular
293
private ImmutableSet<String> genres;
294
}
295
296
// Usage:
297
Library library = Library.builder()
298
.book(new Book("Clean Code"))
299
.book(new Book("Effective Java"))
300
.books(existingBooksList)
301
302
.author("Robert Martin")
303
.author("Joshua Bloch")
304
305
.category("programming", Category.TECHNICAL)
306
.categories(existingCategoriesMap)
307
308
.tag("software")
309
.tag("java")
310
.tags(Arrays.asList("programming", "tutorial"))
311
312
.build();
313
```
314
315
### Builder Method Targeting
316
317
Applying @Builder to constructors or methods instead of classes:
318
319
```java
320
public class MathUtils {
321
322
@Builder(builderMethodName = "rangeBuilder")
323
public static IntRange createRange(int start, int end, boolean inclusive) {
324
return new IntRange(start, end, inclusive);
325
}
326
327
@Builder(builderClassName = "CircleBuilder")
328
public static Circle createCircle(
329
double centerX,
330
double centerY,
331
double radius,
332
@Builder.Default String color = "black"
333
) {
334
return new Circle(centerX, centerY, radius, color);
335
}
336
}
337
338
// Usage:
339
IntRange range = MathUtils.rangeBuilder()
340
.start(1)
341
.end(100)
342
.inclusive(true)
343
.build();
344
345
Circle circle = MathUtils.CircleBuilder()
346
.centerX(0.0)
347
.centerY(0.0)
348
.radius(5.0)
349
.color("red")
350
.build();
351
```
352
353
### Jackson Integration
354
355
Using @Builder with Jackson for JSON deserialization:
356
357
```java
358
import lombok.Builder;
359
import lombok.extern.jackson.Jacksonized;
360
import com.fasterxml.jackson.annotation.JsonProperty;
361
362
@Jacksonized
363
@Builder
364
@Getter
365
public class ApiResponse {
366
@JsonProperty("status_code")
367
private int statusCode;
368
369
private String message;
370
371
@Singular
372
private List<String> errors;
373
374
@JsonProperty("response_data")
375
private Object data;
376
}
377
378
// Jackson can now deserialize JSON directly to builder:
379
// {
380
// "status_code": 200,
381
// "message": "Success",
382
// "errors": ["warning1", "warning2"],
383
// "response_data": {"id": 1, "name": "test"}
384
// }
385
```
386
387
### Advanced Builder Patterns
388
389
Complex builder with inheritance and validation:
390
391
```java
392
@Builder(toBuilder = true)
393
@Getter
394
public class ValidationRequest {
395
@NonNull
396
private String requestId;
397
398
@Builder.Default
399
private Instant timestamp = Instant.now();
400
401
@Singular
402
private Map<String, Object> parameters;
403
404
@Singular("rule")
405
private List<ValidationRule> validationRules;
406
407
@Builder.ObtainVia(method = "getProcessingOptions")
408
private ProcessingOptions options;
409
410
// Custom validation in builder
411
public static class ValidationRequestBuilder {
412
public ValidationRequest build() {
413
if (requestId == null || requestId.trim().isEmpty()) {
414
throw new IllegalArgumentException("Request ID cannot be empty");
415
}
416
417
if (validationRules == null || validationRules.isEmpty()) {
418
throw new IllegalArgumentException("At least one validation rule required");
419
}
420
421
return new ValidationRequest(requestId, timestamp, parameters, validationRules, options);
422
}
423
}
424
}
425
426
// Usage with validation:
427
ValidationRequest request = ValidationRequest.builder()
428
.requestId("REQ-001")
429
.parameter("input", "test data")
430
.parameter("format", "json")
431
.rule(new ValidationRule("required", "input"))
432
.rule(new ValidationRule("format", "json"))
433
.build(); // Throws exception if validation fails
434
```
435
436
## Type Definitions
437
438
```java { .api }
439
/**
440
* Access levels for generated builder classes
441
*/
442
public enum AccessLevel {
443
PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE
444
}
445
446
/**
447
* Placeholder for annotation arrays
448
*/
449
@interface AnyAnnotation {}
450
451
/**
452
* Jackson integration for builder pattern
453
*/
454
@Target(ElementType.TYPE)
455
@interface Jacksonized {}
456
```