0
# Utility Annotations
1
2
Code generation for common Java patterns including null checking, exception handling, synchronization, resource cleanup, and immutable updates. These annotations reduce boilerplate code for frequent programming tasks and improve code safety.
3
4
## Capabilities
5
6
### @NonNull Annotation
7
8
Generates automatic null-checking code for parameters, fields, and local variables, throwing NullPointerException with descriptive messages.
9
10
```java { .api }
11
/**
12
* Generates null-check code for the annotated element.
13
* For parameters: adds null check at method/constructor start
14
* For fields: adds null checks in generated setters and constructors
15
* For local variables: adds null check at assignment
16
*/
17
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
18
@interface NonNull {}
19
```
20
21
**Usage Examples:**
22
23
```java
24
import lombok.NonNull;
25
import lombok.Setter;
26
import lombok.AllArgsConstructor;
27
28
@AllArgsConstructor
29
public class User {
30
@NonNull
31
private String name;
32
33
@Setter
34
@NonNull
35
private String email;
36
37
public void updateProfile(@NonNull String newName, @NonNull String newEmail) {
38
this.name = newName;
39
this.email = newEmail;
40
}
41
42
public void processData(@NonNull List<String> data) {
43
@NonNull String firstItem = data.get(0); // Null check on assignment
44
System.out.println("Processing: " + firstItem);
45
}
46
}
47
48
// Generated constructor with null checks:
49
// public User(String name, String email) {
50
// if (name == null) {
51
// throw new NullPointerException("name is marked non-null but is null");
52
// }
53
// if (email == null) {
54
// throw new NullPointerException("email is marked non-null but is null");
55
// }
56
// this.name = name;
57
// this.email = email;
58
// }
59
60
// Usage:
61
User user = new User("John", "john@example.com"); // OK
62
User invalid = new User(null, "email"); // Throws NullPointerException
63
user.setEmail("new@example.com"); // OK
64
user.setEmail(null); // Throws NullPointerException
65
```
66
67
### @SneakyThrows Annotation
68
69
Bypasses Java's checked exception handling requirements by using bytecode manipulation to throw checked exceptions without declaring them.
70
71
```java { .api }
72
/**
73
* Allows methods to throw checked exceptions without declaring them in throws clause.
74
* Uses bytecode manipulation to circumvent Java's checked exception system.
75
*/
76
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
77
@interface SneakyThrows {
78
/**
79
* Specific exception types to allow sneaky throwing
80
* @return Array of exception types (default: all Throwable types)
81
*/
82
Class<? extends Throwable>[] value() default Throwable.class;
83
}
84
```
85
86
**Usage Examples:**
87
88
```java
89
import lombok.SneakyThrows;
90
import java.io.*;
91
import java.net.URL;
92
93
public class FileProcessor {
94
95
@SneakyThrows
96
public String readFile(String filename) {
97
// No need to declare IOException in throws clause
98
return Files.readString(Paths.get(filename));
99
}
100
101
@SneakyThrows(UnsupportedEncodingException.class)
102
public String encodeUtf8(String text) {
103
// Only bypasses UnsupportedEncodingException
104
return URLEncoder.encode(text, "UTF-8");
105
}
106
107
@SneakyThrows({IOException.class, InterruptedException.class})
108
public String downloadContent(String url) {
109
HttpClient client = HttpClient.newHttpClient();
110
HttpRequest request = HttpRequest.newBuilder()
111
.uri(URI.create(url))
112
.build();
113
114
HttpResponse<String> response = client.send(request,
115
HttpResponse.BodyHandlers.ofString());
116
return response.body();
117
}
118
119
// Can be used with constructors too
120
@SneakyThrows
121
public FileProcessor(String configFile) {
122
Properties props = new Properties();
123
props.load(new FileInputStream(configFile)); // IOException sneakily thrown
124
}
125
}
126
127
// Usage - no try-catch required:
128
FileProcessor processor = new FileProcessor("config.properties");
129
String content = processor.readFile("data.txt");
130
String encoded = processor.encodeUtf8("hello world");
131
```
132
133
### @Synchronized Annotation
134
135
Generates synchronized methods using private lock objects instead of synchronizing on 'this', preventing external interference with locking.
136
137
```java { .api }
138
/**
139
* Synchronizes method execution using private lock objects.
140
* For instance methods: uses private $lock field
141
* For static methods: uses private static $LOCK field
142
* Lock fields are automatically generated if not present.
143
*/
144
@Target(ElementType.METHOD)
145
@interface Synchronized {
146
/**
147
* Name of the lock field to use for synchronization
148
* @return Field name for custom lock (empty = generate default)
149
*/
150
String value() default "";
151
}
152
```
153
154
**Usage Examples:**
155
156
```java
157
import lombok.Synchronized;
158
159
public class Counter {
160
private int count = 0;
161
private final Object customLock = new Object();
162
163
@Synchronized
164
public void increment() {
165
count++;
166
}
167
168
@Synchronized
169
public int getValue() {
170
return count;
171
}
172
173
@Synchronized("customLock")
174
public void reset() {
175
count = 0;
176
}
177
178
// Static method synchronization
179
private static int globalCounter = 0;
180
181
@Synchronized
182
public static void incrementGlobal() {
183
globalCounter++;
184
}
185
}
186
187
// Generated code equivalent:
188
// private final Object $lock = new Object();
189
// private static final Object $LOCK = new Object();
190
//
191
// public void increment() {
192
// synchronized(this.$lock) {
193
// count++;
194
// }
195
// }
196
//
197
// public static void incrementGlobal() {
198
// synchronized(Counter.$LOCK) {
199
// globalCounter++;
200
// }
201
// }
202
//
203
// public void reset() {
204
// synchronized(this.customLock) {
205
// count = 0;
206
// }
207
// }
208
```
209
210
### @Cleanup Annotation
211
212
Automatically generates try-finally blocks to ensure resource cleanup, similar to try-with-resources but more flexible.
213
214
```java { .api }
215
/**
216
* Ensures automatic resource cleanup by wrapping code in try-finally blocks.
217
* Calls the specified cleanup method in the finally block with null checks.
218
*/
219
@Target(ElementType.LOCAL_VARIABLE)
220
@interface Cleanup {
221
/**
222
* Name of the cleanup method to call
223
* @return Method name for cleanup (default: "close")
224
*/
225
String value() default "close";
226
}
227
```
228
229
**Usage Examples:**
230
231
```java
232
import lombok.Cleanup;
233
import java.io.*;
234
235
public class FileManager {
236
237
public void copyFile(String source, String target) throws IOException {
238
@Cleanup FileInputStream in = new FileInputStream(source);
239
@Cleanup FileOutputStream out = new FileOutputStream(target);
240
241
byte[] buffer = new byte[8192];
242
int length;
243
while ((length = in.read(buffer)) != -1) {
244
out.write(buffer, 0, length);
245
}
246
}
247
248
public void processDatabase() throws SQLException {
249
@Cleanup Connection conn = DriverManager.getConnection(url);
250
@Cleanup("close") PreparedStatement stmt = conn.prepareStatement(sql);
251
@Cleanup ResultSet rs = stmt.executeQuery();
252
253
while (rs.next()) {
254
processRow(rs);
255
}
256
}
257
258
// Custom cleanup method
259
public void useCustomResource() {
260
@Cleanup("shutdown") ExecutorService executor = Executors.newFixedThreadPool(4);
261
@Cleanup("release") Semaphore semaphore = new Semaphore(10);
262
263
// Use resources...
264
}
265
}
266
267
// Generated code equivalent for copyFile:
268
// public void copyFile(String source, String target) throws IOException {
269
// FileInputStream in = new FileInputStream(source);
270
// try {
271
// FileOutputStream out = new FileOutputStream(target);
272
// try {
273
// byte[] buffer = new byte[8192];
274
// int length;
275
// while ((length = in.read(buffer)) != -1) {
276
// out.write(buffer, 0, length);
277
// }
278
// } finally {
279
// if (out != null) out.close();
280
// }
281
// } finally {
282
// if (in != null) in.close();
283
// }
284
// }
285
```
286
287
### @With Annotation
288
289
Generates "wither" methods that create copies of immutable objects with one field changed, supporting functional programming patterns.
290
291
```java { .api }
292
/**
293
* Generates 'with' methods for creating modified copies of objects.
294
* Useful for immutable objects and functional programming patterns.
295
*/
296
@Target({ElementType.FIELD, ElementType.TYPE})
297
@interface With {
298
/**
299
* Access level for the generated with method
300
* @return Access level for the with method (default: PUBLIC)
301
*/
302
AccessLevel value() default AccessLevel.PUBLIC;
303
304
/**
305
* Annotations to apply to the generated with method
306
* @return Array of annotations for the generated method
307
*/
308
AnyAnnotation[] onMethod() default {};
309
310
/**
311
* Annotations to apply to the generated method parameter
312
* @return Array of annotations for the generated parameter
313
*/
314
AnyAnnotation[] onParam() default {};
315
}
316
```
317
318
**Usage Examples:**
319
320
```java
321
import lombok.With;
322
import lombok.AllArgsConstructor;
323
import lombok.Getter;
324
import lombok.ToString;
325
326
@With
327
@AllArgsConstructor
328
@Getter
329
@ToString
330
public class Person {
331
private final String name;
332
private final int age;
333
private final String email;
334
private final String city;
335
}
336
337
// Usage:
338
Person original = new Person("John", 30, "john@example.com", "New York");
339
340
Person updated = original
341
.withAge(31)
342
.withCity("San Francisco")
343
.withEmail("john.doe@example.com");
344
345
System.out.println(original); // Original unchanged
346
System.out.println(updated); // New instance with changes
347
```
348
349
Selective field with methods:
350
```java
351
@AllArgsConstructor
352
@Getter
353
public class Configuration {
354
@With
355
private final String host;
356
357
@With
358
private final int port;
359
360
private final String secretKey; // No with method generated
361
362
@With(AccessLevel.PROTECTED)
363
private final boolean debugMode;
364
}
365
366
// Usage:
367
Configuration config = new Configuration("localhost", 8080, "secret", false);
368
Configuration prodConfig = config
369
.withHost("prod.example.com")
370
.withPort(443);
371
// .withSecretKey() - not available
372
// .withDebugMode() - protected access only
373
```
374
375
### @Locked Annotation
376
377
Advanced locking mechanism using ReadWriteLock for fine-grained concurrency control with separate read and write locks.
378
379
```java { .api }
380
/**
381
* Guards method execution with java.util.concurrent.locks.Lock.
382
* Uses ReadWriteLock for more sophisticated locking than @Synchronized.
383
*/
384
@Target(ElementType.METHOD)
385
@interface Locked {
386
/**
387
* Name of the lock field to use
388
* @return Field name for lock (empty = generate default)
389
*/
390
String value() default "";
391
392
/**
393
* Read lock annotation for concurrent read access
394
*/
395
@Target(ElementType.METHOD)
396
@interface Read {
397
/**
398
* Name of the ReadWriteLock field
399
* @return Field name for lock (empty = generate default)
400
*/
401
String value() default "";
402
}
403
404
/**
405
* Write lock annotation for exclusive write access
406
*/
407
@Target(ElementType.METHOD)
408
@interface Write {
409
/**
410
* Name of the ReadWriteLock field
411
* @return Field name for lock (empty = generate default)
412
*/
413
String value() default "";
414
}
415
}
416
```
417
418
**Usage Examples:**
419
420
```java
421
import lombok.Locked;
422
import java.util.concurrent.locks.ReadWriteLock;
423
import java.util.concurrent.locks.ReentrantReadWriteLock;
424
425
public class ThreadSafeCache {
426
private final Map<String, Object> cache = new HashMap<>();
427
private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
428
429
@Locked.Read
430
public Object get(String key) {
431
return cache.get(key);
432
}
433
434
@Locked.Read
435
public boolean containsKey(String key) {
436
return cache.containsKey(key);
437
}
438
439
@Locked.Write
440
public void put(String key, Object value) {
441
cache.put(key, value);
442
}
443
444
@Locked.Write
445
public void remove(String key) {
446
cache.remove(key);
447
}
448
449
@Locked.Read("cacheLock")
450
public Set<String> getKeys() {
451
return new HashSet<>(cache.keySet());
452
}
453
454
@Locked.Write("cacheLock")
455
public void clear() {
456
cache.clear();
457
}
458
}
459
460
// Generated code equivalent:
461
// public Object get(String key) {
462
// this.$lock.readLock().lock();
463
// try {
464
// return cache.get(key);
465
// } finally {
466
// this.$lock.readLock().unlock();
467
// }
468
// }
469
//
470
// public void put(String key, Object value) {
471
// this.$lock.writeLock().lock();
472
// try {
473
// cache.put(key, value);
474
// } finally {
475
// this.$lock.writeLock().unlock();
476
// }
477
// }
478
```
479
480
### Type Inference (val and var)
481
482
Local variable type inference with automatic final modifier application.
483
484
```java { .api }
485
/**
486
* Local variable type inference with final modifier
487
* Equivalent to 'final' + inferred type
488
*/
489
class val {} // Used as: lombok.val variable = expression;
490
491
/**
492
* Local variable type inference without final modifier
493
* Mutable variable with inferred type
494
*/
495
class var {} // Used as: lombok.var variable = expression;
496
```
497
498
**Usage Examples:**
499
500
```java
501
import lombok.val;
502
import lombok.var;
503
504
public class TypeInferenceExample {
505
506
public void demonstrateTypeInference() {
507
// val creates final variables with inferred types
508
val name = "John Doe"; // final String name
509
val age = 30; // final int age
510
val users = new ArrayList<User>(); // final ArrayList<User> users
511
val map = Map.of("key", "value"); // final Map<String, String> map
512
513
// name = "Jane"; // Compile error - final variable
514
515
// var creates mutable variables with inferred types
516
var counter = 0; // int counter
517
var buffer = new StringBuilder(); // StringBuilder buffer
518
var items = new LinkedList<String>(); // LinkedList<String> items
519
520
counter = 10; // OK - mutable
521
buffer.append("text"); // OK
522
523
// Complex type inference
524
val response = restTemplate.exchange(
525
"/api/users",
526
HttpMethod.GET,
527
null,
528
new ParameterizedTypeReference<List<User>>() {}
529
); // final ResponseEntity<List<User>> response
530
531
val optional = Optional.of("value")
532
.filter(s -> s.length() > 3)
533
.map(String::toUpperCase); // final Optional<String> optional
534
535
// Loop variable inference
536
val userList = Arrays.asList(new User("Alice"), new User("Bob"));
537
for (val user : userList) { // final User user
538
System.out.println(user.getName());
539
}
540
541
// Stream operations
542
val result = userList.stream()
543
.filter(u -> u.getAge() > 18)
544
.map(User::getName)
545
.collect(Collectors.toList()); // final List<String> result
546
}
547
}
548
```
549
550
## Type Definitions
551
552
```java { .api }
553
/**
554
* Access levels for generated methods
555
*/
556
public enum AccessLevel {
557
PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE
558
}
559
560
/**
561
* Placeholder for annotation arrays
562
*/
563
@interface AnyAnnotation {}
564
```