0
# Container Element Validation
1
2
Value extraction and validation for generic container types like collections and optionals with type-safe constraint application to container elements.
3
4
## Capabilities
5
6
### Value Extractor
7
8
Interface for extracting values from container types for validation.
9
10
```java { .api }
11
/**
12
* Defines how to extract values from a container type for validation
13
* @param <T> the container type
14
*/
15
interface ValueExtractor<T> {
16
/**
17
* Extract values from the container and pass them to the receiver
18
* @param originalValue the container instance
19
* @param receiver receives extracted values for validation
20
*/
21
void extractValues(T originalValue, ValueReceiver receiver);
22
23
/**
24
* Receives values extracted from containers
25
*/
26
interface ValueReceiver {
27
/**
28
* Receive a simple extracted value
29
* @param nodeName name for the path node (can be null)
30
* @param object extracted value
31
*/
32
void value(String nodeName, Object object);
33
34
/**
35
* Receive value from an iterable container
36
* @param nodeName name for the path node (can be null)
37
* @param object extracted value
38
*/
39
void iterableValue(String nodeName, Object object);
40
41
/**
42
* Receive value from an indexed container
43
* @param nodeName name for the path node (can be null)
44
* @param index index of the value
45
* @param object extracted value
46
*/
47
void indexedValue(String nodeName, int index, Object object);
48
49
/**
50
* Receive value from a keyed container (Map)
51
* @param nodeName name for the path node (can be null)
52
* @param key key of the value
53
* @param object extracted value
54
*/
55
void keyedValue(String nodeName, Object key, Object object);
56
}
57
}
58
```
59
60
### Value Extraction Annotations
61
62
Annotations for configuring value extraction behavior.
63
64
```java { .api }
65
/**
66
* Marks the extracted value in a ValueExtractor
67
* Applied to type parameters in ValueExtractor implementations
68
*/
69
@Target({TYPE_USE})
70
@Retention(RUNTIME)
71
@interface ExtractedValue {
72
/**
73
* The type of the extracted value
74
* @return extracted value type
75
*/
76
Class<?> type() default Object.class;
77
}
78
79
/**
80
* Marks types for default unwrapping behavior
81
* Applied to container types that should be automatically unwrapped
82
*/
83
@Target({TYPE})
84
@Retention(RUNTIME)
85
@interface UnwrapByDefault {}
86
```
87
88
### Unwrapping Configuration
89
90
Classes for controlling value unwrapping behavior.
91
92
```java { .api }
93
/**
94
* Unwrapping behavior configuration
95
*/
96
class Unwrapping {
97
/**
98
* Marker class indicating values should be unwrapped for validation
99
*/
100
public static class Unwrap extends Unwrapping {}
101
102
/**
103
* Marker class indicating values should not be unwrapped for validation
104
*/
105
public static class Skip extends Unwrapping {}
106
}
107
```
108
109
**Usage Examples:**
110
111
```java
112
import jakarta.validation.*;
113
import jakarta.validation.constraints.*;
114
import jakarta.validation.valueextraction.*;
115
import java.util.*;
116
117
// 1. Container element validation with built-in extractors
118
public class ContainerValidationExample {
119
// Validate elements in a List
120
private List<@NotNull @Email String> emails;
121
122
// Validate elements in a Set
123
private Set<@Valid @NotNull User> users;
124
125
// Validate Map keys and values
126
private Map<@NotBlank String, @Positive Integer> scores;
127
128
// Validate Optional content
129
private Optional<@NotNull @Valid User> optionalUser;
130
131
// Validate nested containers
132
private List<Set<@NotNull String>> nestedContainer;
133
}
134
135
// 2. Custom value extractor
136
public class CustomContainer<T> {
137
private T value;
138
139
public T getValue() { return value; }
140
public void setValue(T value) { this.value = value; }
141
}
142
143
// Value extractor for CustomContainer
144
public class CustomContainerValueExtractor
145
implements ValueExtractor<CustomContainer<@ExtractedValue ?>> {
146
147
@Override
148
public void extractValues(CustomContainer<?> originalValue, ValueReceiver receiver) {
149
if (originalValue != null) {
150
receiver.value("value", originalValue.getValue());
151
}
152
}
153
}
154
155
// 3. Register custom extractor in configuration
156
public class CustomValidationConfiguration {
157
public Validator createValidator() {
158
Configuration<?> config = Validation.byDefaultProvider().configure();
159
config.addValueExtractor(new CustomContainerValueExtractor());
160
161
ValidatorFactory factory = config.buildValidatorFactory();
162
return factory.getValidator();
163
}
164
}
165
166
// 4. Usage with custom container
167
public class MyService {
168
@Valid
169
private CustomContainer<@NotNull @Email String> containerizedEmail;
170
171
@Valid
172
private CustomContainer<@Valid User> containerizedUser;
173
}
174
175
// 5. Complex container validation
176
public class ComplexContainerExample {
177
// Nested generics with constraints
178
private Map<@NotBlank String, List<@Valid @NotNull Product>> productsByCategory;
179
180
// Optional with nested container
181
private Optional<List<@NotNull @Size(min=1, max=100) String>> optionalStringList;
182
183
// Custom container with validation
184
@Valid
185
private CustomContainer<List<@Email String>> emailContainer;
186
}
187
188
// 6. Validation example
189
public class ContainerValidationDemo {
190
private Validator validator;
191
192
public ContainerValidationDemo() {
193
// Create validator with custom extractor
194
Configuration<?> config = Validation.byDefaultProvider().configure();
195
config.addValueExtractor(new CustomContainerValueExtractor());
196
ValidatorFactory factory = config.buildValidatorFactory();
197
this.validator = factory.getValidator();
198
}
199
200
public void validateContainers() {
201
// Create test object with invalid container elements
202
ContainerValidationExample example = new ContainerValidationExample();
203
204
// List with null and invalid emails
205
example.setEmails(Arrays.asList(null, "invalid-email", "valid@example.com"));
206
207
// Map with blank key and negative value
208
Map<String, Integer> scores = new HashMap<>();
209
scores.put("", -5); // Invalid key and value
210
scores.put("valid-key", 100); // Valid entry
211
example.setScores(scores);
212
213
// Validate
214
Set<ConstraintViolation<ContainerValidationExample>> violations =
215
validator.validate(example);
216
217
for (ConstraintViolation<ContainerValidationExample> violation : violations) {
218
System.out.println("Path: " + violation.getPropertyPath());
219
System.out.println("Message: " + violation.getMessage());
220
System.out.println("Invalid value: " + violation.getInvalidValue());
221
System.out.println("---");
222
}
223
224
// Expected output shows path like:
225
// emails[0] - NotNull violation
226
// emails[1] - Email violation
227
// scores<K>[].key - NotBlank violation
228
// scores<>[].value - Positive violation
229
}
230
}
231
```