0
# Property Path Navigation
1
2
Hibernate-specific extensions to Jakarta Validation property path nodes providing access to actual property and container element values during validation for enhanced error reporting and debugging.
3
4
## Capabilities
5
6
### Property Node with Value Access
7
8
Property node extension providing access to the actual property value.
9
10
```java { .api }
11
package org.hibernate.validator.path;
12
13
import jakarta.validation.Path;
14
15
/**
16
* Node representing a property with Hibernate Validator specific functionality.
17
* Extends standard Jakarta Validation PropertyNode with value access and additional
18
* Hibernate-specific container metadata.
19
*/
20
interface PropertyNode extends Path.PropertyNode {
21
/**
22
* Get the value of the bean property represented by this node.
23
* Returns the actual value that was validated.
24
*
25
* @return property value or null if not available
26
*/
27
Object getValue();
28
29
/**
30
* Get the container class (e.g., List, Map, Optional) of this property node.
31
* Hibernate-specific extension.
32
*
33
* @return container class or null if not a container element
34
* @since 6.0
35
*/
36
@Incubating
37
Class<?> getContainerClass();
38
39
/**
40
* Get the type argument index in the container's generic type declaration.
41
* Hibernate-specific extension.
42
*
43
* @return type argument index or null if not a container element
44
* @since 6.0
45
*/
46
@Incubating
47
Integer getTypeArgumentIndex();
48
49
// Inherited from Path.PropertyNode:
50
// String getName()
51
// Integer getIndex()
52
// Object getKey()
53
// ElementKind getKind()
54
// boolean isInIterable()
55
}
56
```
57
58
**Usage Example:**
59
60
```java
61
import org.hibernate.validator.path.PropertyNode;
62
import jakarta.validation.*;
63
import java.util.Set;
64
65
class Product {
66
@Min(10)
67
private int quantity;
68
69
@NotNull
70
private String name;
71
72
// getters/setters...
73
}
74
75
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
76
77
Product product = new Product();
78
product.setQuantity(5); // Invalid
79
product.setName(null); // Invalid
80
81
Set<ConstraintViolation<Product>> violations = validator.validate(product);
82
83
for (ConstraintViolation<Product> violation : violations) {
84
Path propertyPath = violation.getPropertyPath();
85
86
for (Path.Node node : propertyPath) {
87
if (node.getKind() == ElementKind.PROPERTY) {
88
// Cast to Hibernate PropertyNode
89
PropertyNode propertyNode = node.as(PropertyNode.class);
90
91
System.out.println("Property: " + propertyNode.getName());
92
System.out.println("Invalid value: " + propertyNode.getValue());
93
System.out.println("Message: " + violation.getMessage());
94
System.out.println("---");
95
}
96
}
97
}
98
99
// Output:
100
// Property: quantity
101
// Invalid value: 5
102
// Message: must be greater than or equal to 10
103
// ---
104
// Property: name
105
// Invalid value: null
106
// Message: must not be null
107
// ---
108
```
109
110
### Container Element Node with Value Access
111
112
Container element node extension providing access to the actual container element value.
113
114
```java { .api }
115
package org.hibernate.validator.path;
116
117
import jakarta.validation.Path;
118
119
/**
120
* Node representing a container element with Hibernate Validator specific functionality.
121
* Extends standard Jakarta Validation ContainerElementNode with value access and additional
122
* Hibernate-specific container metadata.
123
*/
124
interface ContainerElementNode extends Path.ContainerElementNode {
125
/**
126
* Get the value of the container element represented by this node.
127
* Returns the actual element value from the container.
128
*
129
* @return container element value or null if not available
130
*/
131
Object getValue();
132
133
/**
134
* Get the container class (e.g., List, Map, Optional) of this container element node.
135
* Hibernate-specific extension.
136
*
137
* @return container class or null if not available
138
* @since 6.0
139
*/
140
@Incubating
141
Class<?> getContainerClass();
142
143
/**
144
* Get the type argument index in the container's generic type declaration.
145
* Hibernate-specific extension.
146
*
147
* @return type argument index or null if not available
148
* @since 6.0
149
*/
150
@Incubating
151
Integer getTypeArgumentIndex();
152
153
// Inherited from Path.ContainerElementNode:
154
// String getName()
155
// Integer getIndex()
156
// Object getKey()
157
// ElementKind getKind()
158
// boolean isInIterable()
159
}
160
```
161
162
**Usage Example:**
163
164
```java
165
import org.hibernate.validator.path.ContainerElementNode;
166
import jakarta.validation.*;
167
import jakarta.validation.constraints.*;
168
import java.util.*;
169
170
class ShoppingCart {
171
@NotNull
172
@Size(min = 1)
173
private List<@NotNull @Min(1) Integer> itemQuantities;
174
175
public ShoppingCart(List<Integer> itemQuantities) {
176
this.itemQuantities = itemQuantities;
177
}
178
179
// getters/setters...
180
}
181
182
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
183
184
// Create cart with invalid data
185
ShoppingCart cart = new ShoppingCart(Arrays.asList(1, 0, 5, null, -2));
186
187
Set<ConstraintViolation<ShoppingCart>> violations = validator.validate(cart);
188
189
for (ConstraintViolation<ShoppingCart> violation : violations) {
190
Path propertyPath = violation.getPropertyPath();
191
192
for (Path.Node node : propertyPath) {
193
if (node.getKind() == ElementKind.CONTAINER_ELEMENT) {
194
// Cast to Hibernate ContainerElementNode
195
ContainerElementNode containerNode = node.as(ContainerElementNode.class);
196
197
System.out.println("Container: " + containerNode.getContainerClass().getSimpleName());
198
System.out.println("Index: " + containerNode.getIndex());
199
System.out.println("Invalid element value: " + containerNode.getValue());
200
System.out.println("Message: " + violation.getMessage());
201
System.out.println("Full path: " + propertyPath);
202
System.out.println("---");
203
}
204
}
205
}
206
207
// Output includes:
208
// Container: List
209
// Index: 1
210
// Invalid element value: 0
211
// Message: must be greater than or equal to 1
212
// Full path: itemQuantities[1].<list element>
213
// ---
214
// Container: List
215
// Index: 3
216
// Invalid element value: null
217
// Message: must not be null
218
// Full path: itemQuantities[3].<list element>
219
// ---
220
```
221
222
## Complete Property Path Navigation Example
223
224
```java
225
import org.hibernate.validator.path.PropertyNode;
226
import org.hibernate.validator.path.ContainerElementNode;
227
import jakarta.validation.*;
228
import jakarta.validation.constraints.*;
229
import java.util.*;
230
231
// Complex nested structure
232
class Order {
233
@Valid
234
private Customer customer;
235
236
@NotNull
237
@Size(min = 1)
238
private List<@Valid OrderItem> items;
239
240
private Map<@NotBlank String, @Valid @NotNull Address> deliveryAddresses;
241
242
// getters/setters...
243
}
244
245
class Customer {
246
@NotBlank
247
private String name;
248
249
250
private String email;
251
252
// getters/setters...
253
}
254
255
class OrderItem {
256
@NotNull
257
private String productId;
258
259
@Min(1)
260
private int quantity;
261
262
@DecimalMin("0.01")
263
private BigDecimal price;
264
265
// getters/setters...
266
}
267
268
class Address {
269
@NotBlank
270
private String street;
271
272
@NotBlank
273
private String city;
274
275
// getters/setters...
276
}
277
278
// Validate and navigate paths
279
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
280
281
Order order = new Order();
282
// ... set up invalid data ...
283
284
Set<ConstraintViolation<Order>> violations = validator.validate(order);
285
286
for (ConstraintViolation<Order> violation : violations) {
287
System.out.println("\n=== Constraint Violation ===");
288
System.out.println("Message: " + violation.getMessage());
289
System.out.println("Root bean: " + violation.getRootBeanClass().getSimpleName());
290
System.out.println("Property path: " + violation.getPropertyPath());
291
System.out.println("Invalid value: " + violation.getInvalidValue());
292
293
System.out.println("\nPath details:");
294
295
for (Path.Node node : violation.getPropertyPath()) {
296
System.out.println(" Node kind: " + node.getKind());
297
System.out.println(" Node name: " + node.getName());
298
299
switch (node.getKind()) {
300
case PROPERTY:
301
PropertyNode propNode = node.as(PropertyNode.class);
302
System.out.println(" Property value: " + propNode.getValue());
303
break;
304
305
case CONTAINER_ELEMENT:
306
ContainerElementNode containerNode = node.as(ContainerElementNode.class);
307
System.out.println(" Container class: " +
308
containerNode.getContainerClass().getSimpleName());
309
System.out.println(" Element index: " + containerNode.getIndex());
310
System.out.println(" Element key: " + containerNode.getKey());
311
System.out.println(" Element value: " + containerNode.getValue());
312
System.out.println(" Type argument index: " +
313
containerNode.getTypeArgumentIndex());
314
break;
315
316
case BEAN:
317
System.out.println(" Bean node");
318
break;
319
320
case PARAMETER:
321
System.out.println(" Parameter index: " + node.as(Path.ParameterNode.class).getParameterIndex());
322
break;
323
324
case RETURN_VALUE:
325
System.out.println(" Return value node");
326
break;
327
}
328
329
if (node.isInIterable()) {
330
System.out.println(" In iterable: true");
331
System.out.println(" Index: " + node.getIndex());
332
System.out.println(" Key: " + node.getKey());
333
}
334
335
System.out.println();
336
}
337
}
338
339
// Example output for items[1].quantity violation:
340
// === Constraint Violation ===
341
// Message: must be greater than or equal to 1
342
// Root bean: Order
343
// Property path: items[1].quantity
344
// Invalid value: 0
345
//
346
// Path details:
347
// Node kind: PROPERTY
348
// Node name: items
349
// Property value: [OrderItem@123, OrderItem@456]
350
//
351
// Node kind: CONTAINER_ELEMENT
352
// Node name: <list element>
353
// Container class: List
354
// Element index: 1
355
// Element key: null
356
// Element value: OrderItem@456
357
// Type argument index: 0
358
//
359
// Node kind: PROPERTY
360
// Node name: quantity
361
// Property value: 0
362
```
363
364
## Path Navigation for Method Validation
365
366
```java
367
import org.hibernate.validator.path.PropertyNode;
368
import jakarta.validation.*;
369
import jakarta.validation.executable.ExecutableValidator;
370
import java.lang.reflect.Method;
371
import java.util.Set;
372
373
class UserService {
374
public User createUser(
375
@NotBlank String username,
376
@Email String email,
377
@Min(18) int age
378
) {
379
return new User(username, email, age);
380
}
381
382
@Valid
383
@NotNull
384
public User getUser(@NotNull String id) {
385
return new User("test", "test@example.com", 25);
386
}
387
}
388
389
// Validate method parameters
390
ExecutableValidator execValidator = validator.forExecutables();
391
UserService service = new UserService();
392
393
try {
394
Method method = UserService.class.getMethod("createUser",
395
String.class, String.class, int.class);
396
397
Object[] parameters = {"", "invalid-email", 15}; // Invalid parameters
398
399
Set<ConstraintViolation<UserService>> violations =
400
execValidator.validateParameters(service, method, parameters);
401
402
for (ConstraintViolation<UserService> violation : violations) {
403
System.out.println("Violation on: " + violation.getPropertyPath());
404
405
for (Path.Node node : violation.getPropertyPath()) {
406
if (node.getKind() == ElementKind.METHOD) {
407
System.out.println("Method: " + node.getName());
408
} else if (node.getKind() == ElementKind.PARAMETER) {
409
Path.ParameterNode paramNode = node.as(Path.ParameterNode.class);
410
System.out.println("Parameter index: " + paramNode.getParameterIndex());
411
System.out.println("Parameter name: " + paramNode.getName());
412
System.out.println("Invalid value: " + violation.getInvalidValue());
413
}
414
}
415
System.out.println("Message: " + violation.getMessage());
416
System.out.println("---");
417
}
418
} catch (NoSuchMethodException e) {
419
e.printStackTrace();
420
}
421
422
// Output:
423
// Violation on: createUser.arg0
424
// Method: createUser
425
// Parameter index: 0
426
// Parameter name: arg0
427
// Invalid value:
428
// Message: must not be blank
429
// ---
430
// Violation on: createUser.arg1
431
// Method: createUser
432
// Parameter index: 1
433
// Parameter name: arg1
434
// Invalid value: invalid-email
435
// Message: must be a well-formed email address
436
// ---
437
```
438
439
## Accessing Values for Custom Error Reporting
440
441
```java
442
import org.hibernate.validator.path.PropertyNode;
443
import org.hibernate.validator.path.ContainerElementNode;
444
import jakarta.validation.*;
445
import java.util.*;
446
447
/**
448
* Custom error reporter that extracts detailed information from property paths.
449
*/
450
class DetailedErrorReporter {
451
452
public List<ErrorDetail> generateErrorReport(Set<ConstraintViolation<?>> violations) {
453
List<ErrorDetail> errors = new ArrayList<>();
454
455
for (ConstraintViolation<?> violation : violations) {
456
ErrorDetail detail = new ErrorDetail();
457
detail.setMessage(violation.getMessage());
458
detail.setInvalidValue(violation.getInvalidValue());
459
detail.setPropertyPath(violation.getPropertyPath().toString());
460
461
// Extract detailed path information
462
List<PathSegment> pathSegments = new ArrayList<>();
463
464
for (Path.Node node : violation.getPropertyPath()) {
465
PathSegment segment = new PathSegment();
466
segment.setKind(node.getKind().name());
467
segment.setName(node.getName());
468
469
// Get actual value for properties and container elements
470
if (node.getKind() == ElementKind.PROPERTY) {
471
PropertyNode propNode = node.as(PropertyNode.class);
472
segment.setValue(propNode.getValue());
473
} else if (node.getKind() == ElementKind.CONTAINER_ELEMENT) {
474
ContainerElementNode containerNode = node.as(ContainerElementNode.class);
475
segment.setValue(containerNode.getValue());
476
segment.setContainerType(containerNode.getContainerClass().getSimpleName());
477
segment.setIndex(containerNode.getIndex());
478
segment.setKey(containerNode.getKey());
479
}
480
481
pathSegments.add(segment);
482
}
483
484
detail.setPathSegments(pathSegments);
485
errors.add(detail);
486
}
487
488
return errors;
489
}
490
}
491
492
class ErrorDetail {
493
private String message;
494
private Object invalidValue;
495
private String propertyPath;
496
private List<PathSegment> pathSegments;
497
498
// getters/setters...
499
}
500
501
class PathSegment {
502
private String kind;
503
private String name;
504
private Object value;
505
private String containerType;
506
private Integer index;
507
private Object key;
508
509
// getters/setters...
510
}
511
512
// Usage
513
DetailedErrorReporter reporter = new DetailedErrorReporter();
514
Set<ConstraintViolation<Order>> violations = validator.validate(order);
515
List<ErrorDetail> errors = reporter.generateErrorReport(violations);
516
517
// Convert to JSON, log, display in UI, etc.
518
```
519