or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

builder-pattern.mdconstructors.mddata-classes.mdexperimental.mdimmutable-patterns.mdindex.mdlogging.mdobject-methods.mdproperty-access.mdtype-inference.mdutilities.md

builder-pattern.mddocs/

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

```