0
# Attribute Customization
1
2
Annotations for customizing individual attributes including default values, lazy computation, validation, and parameter ordering. These annotations provide fine-grained control over how each attribute behaves in generated immutable classes.
3
4
## Capabilities
5
6
### Default Values
7
8
Provide default values for optional attributes in builders.
9
10
```java { .api }
11
/**
12
* Marks a method as providing a default value for an attribute.
13
* The method body will be used as the default value in builders
14
* when the attribute is not explicitly set.
15
*/
16
@Target(ElementType.METHOD)
17
@Retention(RetentionPolicy.SOURCE)
18
@interface Value.Default {}
19
```
20
21
**Usage Example:**
22
23
```java
24
import org.immutables.value.Value;
25
import java.time.Duration;
26
27
@Value.Immutable
28
public interface ServerConfig {
29
String host();
30
31
@Value.Default
32
default int port() {
33
return 8080;
34
}
35
36
@Value.Default
37
default boolean ssl() {
38
return false;
39
}
40
41
@Value.Default
42
default Duration timeout() {
43
return Duration.ofSeconds(30);
44
}
45
}
46
47
// Usage - port, ssl, and timeout use defaults
48
ServerConfig config = ImmutableServerConfig.builder()
49
.host("localhost")
50
.build();
51
```
52
53
### Derived Attributes
54
55
Computed attributes that are stored in fields for performance.
56
57
```java { .api }
58
/**
59
* Marks an attribute as derived/computed. The method implementation
60
* provides the computation logic, and the result is cached in a field
61
* for performance.
62
*/
63
@Target(ElementType.METHOD)
64
@Retention(RetentionPolicy.SOURCE)
65
@interface Value.Derived {}
66
```
67
68
**Usage Example:**
69
70
```java
71
import org.immutables.value.Value;
72
import java.time.LocalDate;
73
import java.time.Period;
74
75
@Value.Immutable
76
public interface Person {
77
String firstName();
78
String lastName();
79
LocalDate birthDate();
80
81
@Value.Derived
82
default String fullName() {
83
return firstName() + " " + lastName();
84
}
85
86
@Value.Derived
87
default int age() {
88
return Period.between(birthDate(), LocalDate.now()).getYears();
89
}
90
}
91
92
// Derived attributes are computed once and cached
93
Person person = ImmutablePerson.builder()
94
.firstName("Alice")
95
.lastName("Smith")
96
.birthDate(LocalDate.of(1990, 5, 15))
97
.build();
98
99
String name = person.fullName(); // Computed once, cached
100
int age = person.age(); // Computed once, cached
101
```
102
103
### Lazy Computation
104
105
Thread-safe lazy computed attributes for expensive operations.
106
107
```java { .api }
108
/**
109
* Marks an attribute as lazily computed. The computation is deferred
110
* until first access and is thread-safe. Use for expensive computations
111
* that may not always be needed.
112
*/
113
@Target(ElementType.METHOD)
114
@Retention(RetentionPolicy.SOURCE)
115
@interface Value.Lazy {}
116
```
117
118
**Usage Example:**
119
120
```java
121
import org.immutables.value.Value;
122
import java.util.List;
123
124
@Value.Immutable
125
public interface DataSet {
126
List<Double> values();
127
128
@Value.Lazy
129
default double mean() {
130
return values().stream()
131
.mapToDouble(Double::doubleValue)
132
.average()
133
.orElse(0.0);
134
}
135
136
@Value.Lazy
137
default double standardDeviation() {
138
double mean = mean();
139
return Math.sqrt(
140
values().stream()
141
.mapToDouble(v -> Math.pow(v - mean, 2))
142
.average()
143
.orElse(0.0)
144
);
145
}
146
}
147
148
// Expensive computations are deferred until needed
149
DataSet data = ImmutableDataSet.builder()
150
.addValues(1.0, 2.0, 3.0, 4.0, 5.0)
151
.build();
152
153
// Only computed when first accessed, then cached
154
double mean = data.mean();
155
double stdDev = data.standardDeviation();
156
```
157
158
### Constructor Parameters
159
160
Control parameter ordering and inclusion in generated constructors.
161
162
```java { .api }
163
/**
164
* Marks an accessor method as a constructor parameter.
165
* Controls the order of parameters in generated constructors
166
* and factory methods.
167
*/
168
@Target(ElementType.METHOD)
169
@Retention(RetentionPolicy.SOURCE)
170
@interface Value.Parameter {
171
/** Order of parameter in constructor (lower values come first) */
172
int order() default 0;
173
}
174
```
175
176
**Usage Example:**
177
178
```java
179
@Value.Immutable
180
public interface Point3D {
181
@Value.Parameter(order = 1)
182
double x();
183
184
@Value.Parameter(order = 2)
185
double y();
186
187
@Value.Parameter(order = 3)
188
double z();
189
190
// Not a constructor parameter
191
@Value.Derived
192
default double magnitude() {
193
return Math.sqrt(x() * x() + y() * y() + z() * z());
194
}
195
}
196
197
// Generated constructor respects parameter ordering
198
Point3D point = ImmutablePoint3D.of(1.0, 2.0, 3.0); // x, y, z order
199
```
200
201
### Validation
202
203
Validation method invocation for instance validation.
204
205
```java { .api }
206
/**
207
* Marks a method as a validation method. The method will be called
208
* during instance construction to validate the object state.
209
* Should throw an exception if validation fails.
210
*/
211
@Target(ElementType.METHOD)
212
@Retention(RetentionPolicy.SOURCE)
213
@interface Value.Check {}
214
```
215
216
**Usage Example:**
217
218
```java
219
@Value.Immutable
220
public interface Rectangle {
221
double width();
222
double height();
223
224
@Value.Check
225
default void validate() {
226
if (width() <= 0) {
227
throw new IllegalArgumentException("Width must be positive");
228
}
229
if (height() <= 0) {
230
throw new IllegalArgumentException("Height must be positive");
231
}
232
}
233
234
@Value.Derived
235
default double area() {
236
return width() * height();
237
}
238
}
239
240
// Validation is automatically called during construction
241
try {
242
Rectangle rect = ImmutableRectangle.builder()
243
.width(-1.0) // Invalid!
244
.height(5.0)
245
.build(); // Throws IllegalArgumentException
246
} catch (IllegalArgumentException e) {
247
// Handle validation error
248
}
249
```
250
251
### Auxiliary Attributes
252
253
Exclude attributes from equals, hashCode, and toString methods.
254
255
```java { .api }
256
/**
257
* Marks an attribute as auxiliary. Auxiliary attributes are excluded
258
* from equals(), hashCode(), and toString() methods but are still
259
* part of the immutable object.
260
*/
261
@Target(ElementType.METHOD)
262
@Retention(RetentionPolicy.SOURCE)
263
@interface Value.Auxiliary {}
264
```
265
266
**Usage Example:**
267
268
```java
269
@Value.Immutable
270
public interface CacheEntry {
271
String key();
272
String value();
273
274
@Value.Auxiliary // Not part of equality/hash
275
Instant createdAt();
276
277
@Value.Auxiliary // Not part of equality/hash
278
int accessCount();
279
}
280
281
// Two entries with same key/value are equal regardless of auxiliary fields
282
CacheEntry entry1 = ImmutableCacheEntry.builder()
283
.key("user:123")
284
.value("Alice")
285
.createdAt(Instant.now())
286
.accessCount(5)
287
.build();
288
289
CacheEntry entry2 = ImmutableCacheEntry.builder()
290
.key("user:123")
291
.value("Alice")
292
.createdAt(Instant.now().minusSeconds(10)) // Different time
293
.accessCount(2) // Different count
294
.build();
295
296
// Still equal because auxiliary fields are ignored
297
assert entry1.equals(entry2); // true
298
assert entry1.hashCode() == entry2.hashCode(); // true
299
```
300
301
## Combining Annotations
302
303
Multiple attribute annotations can be combined for complex behavior:
304
305
```java
306
@Value.Immutable
307
public interface ComplexType {
308
String name();
309
310
@Value.Default
311
@Value.Parameter(order = 1)
312
default String category() {
313
return "default";
314
}
315
316
@Value.Lazy
317
@Value.Auxiliary
318
default String expensiveComputation() {
319
// Expensive operation not included in equals/hash
320
return performExpensiveCalculation();
321
}
322
323
@Value.Derived
324
@Value.Check
325
default String normalizedName() {
326
String normalized = name().toLowerCase().trim();
327
if (normalized.isEmpty()) {
328
throw new IllegalArgumentException("Name cannot be empty");
329
}
330
return normalized;
331
}
332
}
333
```