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
```