or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

builder.mdconstructors.mddata-classes.mdexperimental.mdindex.mdlogging.mdproperty-access.mdutilities.md

experimental.mddocs/

0

# Experimental Features

1

2

Advanced and experimental features including utility classes, enhanced builders for inheritance, field name constants, and method delegation. These features provide cutting-edge functionality that may become part of the core lombok API in future releases.

3

4

**Warning:** Experimental features may change behavior or API in future lombok versions. Use with caution in production code.

5

6

## Capabilities

7

8

### @UtilityClass Annotation

9

10

Creates utility classes with static-only members and prevents instantiation by generating a private constructor that throws an exception.

11

12

```java { .api }

13

/**

14

* Transforms a class into a utility class by:

15

* - Making the class final

16

* - Making all methods, fields, and inner classes static

17

* - Generating a private constructor that throws UnsupportedOperationException

18

* - Preventing manual constructor declaration

19

*/

20

@Target(ElementType.TYPE)

21

@interface UtilityClass {}

22

```

23

24

**Usage Examples:**

25

26

```java

27

import lombok.experimental.UtilityClass;

28

29

@UtilityClass

30

public class MathUtils {

31

32

// All fields become static automatically

33

private final double PI_SQUARED = Math.PI * Math.PI;

34

35

// All methods become static automatically

36

public double calculateCircleArea(double radius) {

37

return Math.PI * radius * radius;

38

}

39

40

public double calculateSphereVolume(double radius) {

41

return (4.0 / 3.0) * Math.PI * Math.pow(radius, 3);

42

}

43

44

public boolean isPrime(int number) {

45

if (number < 2) return false;

46

for (int i = 2; i <= Math.sqrt(number); i++) {

47

if (number % i == 0) return false;

48

}

49

return true;

50

}

51

52

// Nested classes also become static

53

public class Constants {

54

public final double GOLDEN_RATIO = 1.618033988749895;

55

public final double EULER_NUMBER = 2.718281828459045;

56

}

57

}

58

59

// Generated class equivalent:

60

// public final class MathUtils {

61

// private static final double PI_SQUARED = Math.PI * Math.PI;

62

//

63

// private MathUtils() {

64

// throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");

65

// }

66

//

67

// public static double calculateCircleArea(double radius) { ... }

68

// public static double calculateSphereVolume(double radius) { ... }

69

// public static boolean isPrime(int number) { ... }

70

//

71

// public static class Constants { ... }

72

// }

73

74

// Usage:

75

double area = MathUtils.calculateCircleArea(5.0);

76

boolean isPrime = MathUtils.isPrime(17);

77

double golden = MathUtils.Constants.GOLDEN_RATIO;

78

```

79

80

String utilities example:

81

```java

82

@UtilityClass

83

public class StringUtils {

84

85

public boolean isBlank(String str) {

86

return str == null || str.trim().isEmpty();

87

}

88

89

public String capitalize(String str) {

90

if (isBlank(str)) return str;

91

return Character.toUpperCase(str.charAt(0)) + str.substring(1).toLowerCase();

92

}

93

94

public String reverse(String str) {

95

return str == null ? null : new StringBuilder(str).reverse().toString();

96

}

97

98

public int countWords(String str) {

99

if (isBlank(str)) return 0;

100

return str.trim().split("\\s+").length;

101

}

102

}

103

```

104

105

### @SuperBuilder Annotation

106

107

Enhanced builder pattern for inheritance hierarchies, allowing builder patterns to work seamlessly with class inheritance.

108

109

```java { .api }

110

/**

111

* Generates builder pattern that works with inheritance.

112

* All classes in the hierarchy must use @SuperBuilder.

113

* Creates two inner builder classes that extend parent builders.

114

*/

115

@Target(ElementType.TYPE)

116

@interface SuperBuilder {

117

/**

118

* Name of the static builder method

119

* @return Builder method name (default: "builder")

120

*/

121

String builderMethodName() default "builder";

122

123

/**

124

* Name of the build method in builder class

125

* @return Build method name (default: "build")

126

*/

127

String buildMethodName() default "build";

128

129

/**

130

* Generate toBuilder() instance method for creating pre-populated builders

131

* All superclasses must also have toBuilder=true

132

* @return Whether to generate toBuilder() method (default: false)

133

*/

134

boolean toBuilder() default false;

135

136

/**

137

* Prefix for builder setter methods

138

* @return Setter prefix (default: empty string)

139

*/

140

String setterPrefix() default "";

141

}

142

```

143

144

**Usage Examples:**

145

146

```java

147

import lombok.experimental.SuperBuilder;

148

import lombok.Getter;

149

import lombok.ToString;

150

151

@SuperBuilder

152

@Getter

153

@ToString

154

public class Animal {

155

private String name;

156

private int age;

157

private String species;

158

}

159

160

@SuperBuilder

161

@Getter

162

@ToString(callSuper = true)

163

public class Mammal extends Animal {

164

private boolean furry;

165

private String habitat;

166

}

167

168

@SuperBuilder

169

@Getter

170

@ToString(callSuper = true)

171

public class Dog extends Mammal {

172

private String breed;

173

private boolean trained;

174

175

@Builder.Default

176

private String sound = "Woof!";

177

}

178

179

// Usage with inheritance:

180

Dog dog = Dog.builder()

181

.name("Buddy") // From Animal

182

.age(3) // From Animal

183

.species("Canis lupus") // From Animal

184

.furry(true) // From Mammal

185

.habitat("Domestic") // From Mammal

186

.breed("Golden Retriever") // From Dog

187

.trained(true) // From Dog

188

.build();

189

190

System.out.println(dog);

191

// Dog(super=Mammal(super=Animal(name=Buddy, age=3, species=Canis lupus), furry=true, habitat=Domestic), breed=Golden Retriever, trained=true, sound=Woof!)

192

```

193

194

Abstract class inheritance:

195

```java

196

@SuperBuilder

197

@Getter

198

public abstract class Vehicle {

199

private String manufacturer;

200

private int year;

201

private String color;

202

203

public abstract void start();

204

}

205

206

@SuperBuilder

207

@Getter

208

@ToString(callSuper = true)

209

public class Car extends Vehicle {

210

private int doors;

211

private String fuelType;

212

213

@Override

214

public void start() {

215

System.out.println("Car engine starting...");

216

}

217

}

218

219

@SuperBuilder

220

@Getter

221

@ToString(callSuper = true)

222

public class ElectricCar extends Car {

223

private int batteryCapacity;

224

private int range;

225

226

@Builder.Default

227

private String fuelType = "Electric";

228

229

@Override

230

public void start() {

231

System.out.println("Electric motor starting silently...");

232

}

233

}

234

235

// Usage:

236

ElectricCar tesla = ElectricCar.builder()

237

.manufacturer("Tesla") // From Vehicle

238

.year(2023) // From Vehicle

239

.color("Red") // From Vehicle

240

.doors(4) // From Car

241

.batteryCapacity(100) // From ElectricCar

242

.range(400) // From ElectricCar

243

.build();

244

```

245

246

### @FieldDefaults Annotation

247

248

Sets default field modifiers for all fields in a class, reducing repetitive access modifier and final declarations.

249

250

```java { .api }

251

/**

252

* Applies default modifiers to all fields in the annotated type.

253

* Fields can opt out using @NonFinal and @PackagePrivate annotations.

254

*/

255

@Target(ElementType.TYPE)

256

@interface FieldDefaults {

257

/**

258

* Access level to apply to package-private fields

259

* @return Default access level (default: NONE - no change)

260

*/

261

AccessLevel level() default AccessLevel.NONE;

262

263

/**

264

* Whether to make instance fields final by default

265

* @return Whether to add final modifier (default: false)

266

*/

267

boolean makeFinal() default false;

268

}

269

270

/**

271

* Excludes field from final modifier when @FieldDefaults(makeFinal=true)

272

*/

273

@Target(ElementType.FIELD)

274

@interface NonFinal {}

275

276

/**

277

* Excludes field from access level change when @FieldDefaults(level=...)

278

*/

279

@Target(ElementType.FIELD)

280

@interface PackagePrivate {}

281

```

282

283

**Usage Examples:**

284

285

```java

286

import lombok.experimental.FieldDefaults;

287

import lombok.experimental.NonFinal;

288

import lombok.AccessLevel;

289

import lombok.AllArgsConstructor;

290

import lombok.Getter;

291

292

@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)

293

@AllArgsConstructor

294

@Getter

295

public class ImmutableUser {

296

String username; // becomes: private final String username;

297

String email; // becomes: private final String email;

298

int age; // becomes: private final int age;

299

Date createdAt; // becomes: private final Date createdAt;

300

301

@NonFinal

302

String lastLoginIp; // becomes: private String lastLoginIp; (not final)

303

304

static int userCount = 0; // Static fields are not affected

305

}

306

307

// Generated equivalent:

308

// public class ImmutableUser {

309

// private final String username;

310

// private final String email;

311

// private final int age;

312

// private final Date createdAt;

313

// private String lastLoginIp;

314

// static int userCount = 0;

315

// }

316

```

317

318

Configuration class example:

319

```java

320

@FieldDefaults(level = AccessLevel.PRIVATE)

321

@Data

322

public class Configuration {

323

String host; // becomes: private String host;

324

int port; // becomes: private int port;

325

boolean sslEnabled; // becomes: private boolean sslEnabled;

326

327

@PackagePrivate

328

String internalConfig; // remains: String internalConfig; (package-private)

329

}

330

```

331

332

### @Accessors Annotation

333

334

Configures the style and behavior of generated getter and setter methods, supporting fluent APIs and method chaining.

335

336

```java { .api }

337

/**

338

* Configures generation style for getters, setters, and with methods.

339

* Can be applied to classes or individual fields.

340

*/

341

@Target({ElementType.TYPE, ElementType.FIELD})

342

@interface Accessors {

343

/**

344

* Generate fluent accessors without get/set prefixes

345

* @return Whether to use fluent naming (fieldName() instead of getFieldName())

346

*/

347

boolean fluent() default false;

348

349

/**

350

* Make setters return 'this' for method chaining

351

* Defaults to true when fluent=true

352

* @return Whether setters should return this for chaining

353

*/

354

boolean chain() default false;

355

356

/**

357

* Make generated accessors final

358

* @return Whether accessors should be final

359

*/

360

boolean makeFinal() default false;

361

362

/**

363

* Prefixes to strip from field names when generating accessor names

364

* @return Array of prefixes to remove

365

*/

366

String[] prefix() default {};

367

}

368

```

369

370

**Usage Examples:**

371

372

```java

373

import lombok.experimental.Accessors;

374

import lombok.Getter;

375

import lombok.Setter;

376

377

@Accessors(fluent = true)

378

@Getter

379

@Setter

380

public class FluentUser {

381

private String name;

382

private int age;

383

private String email;

384

}

385

386

// Generated methods:

387

// public String name() { return this.name; }

388

// public FluentUser name(String name) { this.name = name; return this; }

389

// public int age() { return this.age; }

390

// public FluentUser age(int age) { this.age = age; return this; }

391

// public String email() { return this.email; }

392

// public FluentUser email(String email) { this.email = email; return this; }

393

394

// Usage:

395

FluentUser user = new FluentUser()

396

.name("John")

397

.age(30)

398

.email("john@example.com");

399

400

String name = user.name();

401

int age = user.age();

402

```

403

404

Method chaining with traditional naming:

405

```java

406

@Accessors(chain = true)

407

@Setter

408

@Getter

409

public class ChainableConfig {

410

private String host;

411

private int port;

412

private boolean debug;

413

}

414

415

// Generated setters return 'this':

416

// public ChainableConfig setHost(String host) { this.host = host; return this; }

417

// public ChainableConfig setPort(int port) { this.port = port; return this; }

418

// public ChainableConfig setDebug(boolean debug) { this.debug = debug; return this; }

419

420

// Usage:

421

ChainableConfig config = new ChainableConfig()

422

.setHost("localhost")

423

.setPort(8080)

424

.setDebug(true);

425

```

426

427

Prefix handling:

428

```java

429

@Accessors(prefix = {"f", "m_"})

430

@Getter

431

@Setter

432

public class PrefixedFields {

433

private String fName; // Generates: getName(), setName()

434

private int fAge; // Generates: getAge(), setAge()

435

private String m_email; // Generates: getEmail(), setEmail()

436

private boolean m_active; // Generates: isActive(), setActive()

437

private String regularField; // Generates: getRegularField(), setRegularField()

438

}

439

```

440

441

### @FieldNameConstants Annotation

442

443

Generates compile-time constants for field names, useful for reflection, JPA queries, and avoiding magic strings.

444

445

```java { .api }

446

/**

447

* Generates an inner type containing field name constants.

448

* Can generate either String constants or enum values.

449

*/

450

@Target(ElementType.TYPE)

451

@interface FieldNameConstants {

452

/**

453

* Access level for the generated inner type

454

* @return Access level (default: PUBLIC)

455

*/

456

AccessLevel level() default AccessLevel.PUBLIC;

457

458

/**

459

* Generate enum instead of String constants

460

* @return Whether to generate enum (default: false - String constants)

461

*/

462

boolean asEnum() default false;

463

464

/**

465

* Name of the generated inner type

466

* @return Inner type name (default: "Fields")

467

*/

468

String innerTypeName() default "";

469

470

/**

471

* Only include explicitly marked fields

472

* @return Whether to require explicit inclusion (default: false)

473

*/

474

boolean onlyExplicitlyIncluded() default false;

475

}

476

477

/**

478

* Excludes field from constant generation

479

*/

480

@Target(ElementType.FIELD)

481

@interface FieldNameConstants.Exclude {}

482

483

/**

484

* Explicitly includes field in constant generation

485

*/

486

@Target(ElementType.FIELD)

487

@interface FieldNameConstants.Include {}

488

```

489

490

**Usage Examples:**

491

492

```java

493

import lombok.experimental.FieldNameConstants;

494

495

@FieldNameConstants

496

public class User {

497

private String username;

498

private String email;

499

private int age;

500

private Date lastLogin;

501

502

@FieldNameConstants.Exclude

503

private String password; // No constant generated

504

}

505

506

// Generated inner class:

507

// public static final class Fields {

508

// public static final String username = "username";

509

// public static final String email = "email";

510

// public static final String age = "age";

511

// public static final String lastLogin = "lastLogin";

512

// }

513

514

// Usage in JPA queries:

515

String jpql = "SELECT u FROM User u WHERE u." + User.Fields.username + " = :username";

516

517

// Usage in reflection:

518

Field usernameField = User.class.getDeclaredField(User.Fields.username);

519

520

// Usage in validation:

521

if (errors.containsKey(User.Fields.email)) {

522

handleEmailError();

523

}

524

```

525

526

Enum-based field constants:

527

```java

528

@FieldNameConstants(asEnum = true, innerTypeName = "Property")

529

public class Product {

530

private String name;

531

private double price;

532

private String category;

533

private boolean available;

534

}

535

536

// Generated enum:

537

// public enum Property {

538

// name, price, category, available

539

// }

540

541

// Usage:

542

Map<Product.Property, Object> productData = new HashMap<>();

543

productData.put(Product.Property.name, "Laptop");

544

productData.put(Product.Property.price, 999.99);

545

productData.put(Product.Property.available, true);

546

547

// Switch statements:

548

switch (property) {

549

case name:

550

validateName(value);

551

break;

552

case price:

553

validatePrice(value);

554

break;

555

}

556

```

557

558

Selective inclusion:

559

```java

560

@FieldNameConstants(onlyExplicitlyIncluded = true)

561

public class DatabaseEntity {

562

@FieldNameConstants.Include

563

private Long id;

564

565

@FieldNameConstants.Include

566

private String name;

567

568

private String internalField; // No constant generated

569

570

@FieldNameConstants.Include

571

private Date createdAt;

572

573

private transient String cache; // No constant generated

574

}

575

576

// Only generates constants for: id, name, createdAt

577

```

578

579

### @Delegate Annotation

580

581

Automatically generates delegation methods that forward calls to a delegate field, implementing the delegation pattern.

582

583

```java { .api }

584

/**

585

* Generates delegate methods that forward calls to the annotated field.

586

* All public instance methods of the field's type are delegated.

587

*/

588

@Target({ElementType.FIELD, ElementType.METHOD})

589

@interface Delegate {

590

/**

591

* Specific types to delegate instead of using field type

592

* @return Types whose methods should be delegated

593

*/

594

Class<?>[] types() default {};

595

596

/**

597

* Types whose methods should NOT be delegated

598

* @return Types to exclude from delegation

599

*/

600

Class<?>[] excludes() default {};

601

}

602

```

603

604

**Usage Examples:**

605

606

```java

607

import lombok.experimental.Delegate;

608

import java.util.*;

609

610

public class IntegerList {

611

@Delegate

612

private List<Integer> numbers = new ArrayList<>();

613

614

// Custom business methods

615

public void addEven(int number) {

616

if (number % 2 == 0) {

617

numbers.add(number);

618

}

619

}

620

621

public List<Integer> getEvens() {

622

return numbers.stream()

623

.filter(n -> n % 2 == 0)

624

.collect(Collectors.toList());

625

}

626

}

627

628

// Generated delegation methods:

629

// public boolean add(Integer element) { return numbers.add(element); }

630

// public void add(int index, Integer element) { numbers.add(index, element); }

631

// public boolean remove(Object o) { return numbers.remove(o); }

632

// public Integer remove(int index) { return numbers.remove(index); }

633

// public int size() { return numbers.size(); }

634

// public boolean isEmpty() { return numbers.isEmpty(); }

635

// ... all other List methods

636

637

// Usage:

638

IntegerList intList = new IntegerList();

639

intList.add(1); // Delegated to List.add()

640

intList.add(2); // Delegated to List.add()

641

intList.addEven(3); // Custom method - 3 not added (odd)

642

intList.addEven(4); // Custom method - 4 added (even)

643

int size = intList.size(); // Delegated to List.size()

644

```

645

646

Selective delegation with type specification:

647

```java

648

public class FileManager {

649

@Delegate(types = {AutoCloseable.class})

650

private FileInputStream inputStream;

651

652

@Delegate(types = {Readable.class}, excludes = {Closeable.class})

653

private BufferedReader reader;

654

655

// Only AutoCloseable.close() is delegated from inputStream

656

// Only Readable methods (not Closeable) are delegated from reader

657

}

658

```

659

660

Interface-based delegation:

661

```java

662

interface CustomCollection<T> extends Collection<T> {

663

void addMultiple(T... items);

664

void removeMultiple(T... items);

665

}

666

667

public class MyCollection<T> implements CustomCollection<T> {

668

@Delegate(excludes = {Collection.class})

669

private List<T> delegate = new ArrayList<>();

670

671

@Override

672

public void addMultiple(T... items) {

673

Collections.addAll(delegate, items);

674

}

675

676

@Override

677

public void removeMultiple(T... items) {

678

for (T item : items) {

679

delegate.remove(item);

680

}

681

}

682

683

// All Collection methods are excluded from delegation

684

// Must implement them manually or use another approach

685

}

686

```

687

688

### @ExtensionMethod Annotation

689

690

Enables extension methods from static utility classes, allowing method calls to appear as instance methods on existing types.

691

692

```java { .api }

693

/**

694

* Makes static methods from specified classes available as instance methods.

695

* The first parameter of the static method becomes the instance the method is called on.

696

*/

697

@Target(ElementType.TYPE)

698

@interface ExtensionMethod {

699

/**

700

* Classes containing static methods to expose as extension methods

701

* @return Array of classes with static extension methods

702

*/

703

Class<?>[] value();

704

705

/**

706

* Whether extension methods override existing instance methods

707

* @return If true, extensions override existing methods (default: true)

708

*/

709

boolean suppressBaseMethods() default true;

710

}

711

```

712

713

**Usage Examples:**

714

715

```java

716

import lombok.experimental.ExtensionMethod;

717

718

// Extension methods utility class

719

public class StringExtensions {

720

721

public static boolean isBlank(String str) {

722

return str == null || str.trim().isEmpty();

723

}

724

725

public static String reverse(String str) {

726

if (str == null) return null;

727

return new StringBuilder(str).reverse().toString();

728

}

729

730

public static String truncate(String str, int maxLength) {

731

if (str == null || str.length() <= maxLength) return str;

732

return str.substring(0, maxLength) + "...";

733

}

734

735

public static int wordCount(String str) {

736

if (str == null || str.trim().isEmpty()) return 0;

737

return str.trim().split("\\s+").length;

738

}

739

}

740

741

@ExtensionMethod(StringExtensions.class)

742

public class TextProcessor {

743

744

public void processText(String input) {

745

// Extension methods used as instance methods

746

if (input.isBlank()) { // StringExtensions.isBlank(input)

747

System.out.println("Input is blank");

748

return;

749

}

750

751

String reversed = input.reverse(); // StringExtensions.reverse(input)

752

String truncated = input.truncate(50); // StringExtensions.truncate(input, 50)

753

int words = input.wordCount(); // StringExtensions.wordCount(input)

754

755

System.out.println("Original: " + input);

756

System.out.println("Reversed: " + reversed);

757

System.out.println("Truncated: " + truncated);

758

System.out.println("Word count: " + words);

759

}

760

}

761

```

762

763

Multiple extension classes:

764

```java

765

public class MathExtensions {

766

public static boolean isEven(Integer number) {

767

return number != null && number % 2 == 0;

768

}

769

770

public static boolean isPrime(Integer number) {

771

if (number == null || number < 2) return false;

772

for (int i = 2; i <= Math.sqrt(number); i++) {

773

if (number % i == 0) return false;

774

}

775

return true;

776

}

777

}

778

779

public class CollectionExtensions {

780

public static <T> boolean isNullOrEmpty(Collection<T> collection) {

781

return collection == null || collection.isEmpty();

782

}

783

784

public static <T> T firstOrNull(List<T> list) {

785

return (list == null || list.isEmpty()) ? null : list.get(0);

786

}

787

}

788

789

@ExtensionMethod({MathExtensions.class, CollectionExtensions.class, Arrays.class})

790

public class DataProcessor {

791

792

public void processData() {

793

Integer number = 17;

794

if (number.isEven()) { // MathExtensions.isEven(number)

795

System.out.println("Even number");

796

} else if (number.isPrime()) { // MathExtensions.isPrime(number)

797

System.out.println("Prime number");

798

}

799

800

List<String> items = Arrays.asList("a", "b", "c");

801

if (!items.isNullOrEmpty()) { // CollectionExtensions.isNullOrEmpty(items)

802

String first = items.firstOrNull(); // CollectionExtensions.firstOrNull(items)

803

System.out.println("First item: " + first);

804

}

805

806

int[] array = {3, 1, 4, 1, 5};

807

array.sort(); // Arrays.sort(array) - from JDK

808

}

809

}

810

```

811

812

### Advanced Experimental Features

813

814

Additional experimental annotations for specialized use cases:

815

816

```java

817

import lombok.experimental.*;

818

819

// @WithBy - Functional-style with methods

820

@WithBy

821

public class FunctionalUser {

822

private String name;

823

private int age;

824

}

825

826

// Usage: user.withNameBy(String::toUpperCase)

827

828

// @Tolerate - Allow method conflicts in builders

829

@Builder

830

public class ToleratedClass {

831

private String value;

832

833

@Tolerate

834

public ToleratedClassBuilder value(int intValue) {

835

return value(String.valueOf(intValue));

836

}

837

}

838

839

// @StandardException - Generate standard exception constructors

840

@StandardException

841

public class CustomException extends Exception {}

842

843

// Generates constructors for:

844

// - CustomException()

845

// - CustomException(String message)

846

// - CustomException(Throwable cause)

847

// - CustomException(String message, Throwable cause)

848

```

849

850

## Type Definitions

851

852

```java { .api }

853

/**

854

* Access levels for generated code

855

*/

856

public enum AccessLevel {

857

PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE

858

}

859

```