0
# Service Provider Interfaces
1
2
Comprehensive SPI for extending Hibernate Validator including constraint mapping contributors, dynamic group sequence providers, custom locale resolvers, property node name providers, getter property selection strategies, resource bundle locators, and script evaluator factories.
3
4
## Capabilities
5
6
### Constraint Mapping Contributor SPI
7
8
Contribute constraint mappings programmatically during validator factory creation.
9
10
```java { .api }
11
package org.hibernate.validator.spi.cfg;
12
13
import org.hibernate.validator.cfg.ConstraintMapping;
14
15
/**
16
* Contributes constraint mappings to validator configuration.
17
* Implementations are discovered via ServiceLoader or configured explicitly.
18
*/
19
interface ConstraintMappingContributor {
20
/**
21
* Create and add constraint mappings to builder.
22
*
23
* @param builder constraint mapping builder
24
*/
25
void createConstraintMappings(ConstraintMappingBuilder builder);
26
27
/**
28
* Builder for adding constraint mappings.
29
*/
30
interface ConstraintMappingBuilder {
31
/**
32
* Add new constraint mapping to configuration.
33
*
34
* @return newly created constraint mapping for fluent configuration
35
*/
36
ConstraintMapping addConstraintMapping();
37
}
38
}
39
```
40
41
**Usage Example:**
42
43
```java
44
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
45
import org.hibernate.validator.cfg.ConstraintMapping;
46
import org.hibernate.validator.cfg.defs.*;
47
import static java.lang.annotation.ElementType.*;
48
49
/**
50
* Contributor that adds validation constraints for all User entities.
51
*/
52
public class UserConstraintContributor implements ConstraintMappingContributor {
53
54
@Override
55
public void createConstraintMappings(ConstraintMappingBuilder builder) {
56
// Add user constraints
57
builder.addConstraintMapping()
58
.type(User.class)
59
.field("username")
60
.constraint(new NotBlankDef())
61
.constraint(new LengthDef().min(3).max(50))
62
.field("email")
63
.constraint(new NotBlankDef())
64
.constraint(new EmailDef())
65
.field("age")
66
.constraint(new MinDef().value(18))
67
.constraint(new MaxDef().value(120));
68
69
// Add address constraints
70
builder.addConstraintMapping()
71
.type(Address.class)
72
.field("street")
73
.constraint(new NotBlankDef())
74
.field("zipCode")
75
.constraint(new NotBlankDef())
76
.constraint(new PatternDef().regexp("[0-9]{5}"));
77
}
78
}
79
80
// Register via ServiceLoader:
81
// Create META-INF/services/org.hibernate.validator.spi.cfg.ConstraintMappingContributor
82
// Add line: com.example.UserConstraintContributor
83
84
// Or configure explicitly:
85
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
86
.configure()
87
.addProperty(
88
BaseHibernateValidatorConfiguration.CONSTRAINT_MAPPING_CONTRIBUTORS,
89
"com.example.UserConstraintContributor"
90
)
91
.buildValidatorFactory();
92
```
93
94
### Group Sequence Provider SPI
95
96
Provide default group sequence dynamically at validation time.
97
98
```java { .api }
99
package org.hibernate.validator.spi.group;
100
101
import java.util.List;
102
103
/**
104
* Provides default group sequence for given type dynamically.
105
* Allows group sequence to depend on object state.
106
*
107
* @param <T> type for which to provide default group sequence
108
*/
109
interface DefaultGroupSequenceProvider<T> {
110
/**
111
* Returns validation groups for given object.
112
* The Default group will be replaced by returned groups.
113
*
114
* @param object object being validated
115
* @return list of validation groups
116
*/
117
List<Class<?>> getValidationGroups(T object);
118
}
119
120
/**
121
* Annotation that specifies the DefaultGroupSequenceProvider implementation
122
* for dynamic group sequence resolution.
123
*
124
* Applied to bean types to enable state-dependent validation group ordering.
125
* Cannot be used together with @GroupSequence.
126
*
127
* This is a Hibernate Validator specific annotation (not portable).
128
*
129
* @see DefaultGroupSequenceProvider
130
* @see jakarta.validation.GroupSequence
131
*/
132
package org.hibernate.validator.group;
133
134
import java.lang.annotation.*;
135
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
136
137
@Target({ElementType.TYPE})
138
@Retention(RetentionPolicy.RUNTIME)
139
@interface GroupSequenceProvider {
140
/**
141
* The DefaultGroupSequenceProvider implementation to use.
142
*
143
* @return default group sequence provider class
144
*/
145
Class<? extends DefaultGroupSequenceProvider<?>> value();
146
}
147
```
148
149
**Usage Example:**
150
151
```java
152
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
153
import org.hibernate.validator.group.GroupSequenceProvider;
154
import jakarta.validation.constraints.*;
155
import java.util.*;
156
157
// Define validation groups
158
interface BasicChecks {}
159
interface PremiumChecks {}
160
interface AdminChecks {}
161
162
// Entity with dynamic group sequence
163
@GroupSequenceProvider(UserGroupSequenceProvider.class)
164
class User {
165
@NotNull
166
private String username;
167
168
@NotNull(groups = BasicChecks.class)
169
@Email(groups = BasicChecks.class)
170
private String email;
171
172
@NotNull(groups = PremiumChecks.class)
173
@Pattern(regexp = "\\d{3}-\\d{3}-\\d{4}", groups = PremiumChecks.class)
174
private String phoneNumber;
175
176
@NotNull(groups = AdminChecks.class)
177
private String adminToken;
178
179
private UserType userType;
180
181
// getters/setters...
182
}
183
184
enum UserType {
185
BASIC, PREMIUM, ADMIN
186
}
187
188
/**
189
* Provides dynamic group sequence based on user type.
190
*/
191
class UserGroupSequenceProvider implements DefaultGroupSequenceProvider<User> {
192
193
@Override
194
public List<Class<?>> getValidationGroups(User user) {
195
List<Class<?>> groups = new ArrayList<>();
196
197
// Always include Default group
198
groups.add(User.class);
199
200
if (user == null) {
201
return groups;
202
}
203
204
// Add groups based on user type
205
switch (user.getUserType()) {
206
case BASIC:
207
groups.add(BasicChecks.class);
208
break;
209
case PREMIUM:
210
groups.add(BasicChecks.class);
211
groups.add(PremiumChecks.class);
212
break;
213
case ADMIN:
214
groups.add(BasicChecks.class);
215
groups.add(PremiumChecks.class);
216
groups.add(AdminChecks.class);
217
break;
218
}
219
220
return groups;
221
}
222
}
223
224
// Usage
225
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
226
227
// Basic user - only basic checks
228
User basicUser = new User();
229
basicUser.setUserType(UserType.BASIC);
230
Set<ConstraintViolation<User>> violations = validator.validate(basicUser);
231
// Validates: username, email
232
233
// Premium user - basic + premium checks
234
User premiumUser = new User();
235
premiumUser.setUserType(UserType.PREMIUM);
236
violations = validator.validate(premiumUser);
237
// Validates: username, email, phoneNumber
238
239
// Admin user - all checks
240
User adminUser = new User();
241
adminUser.setUserType(UserType.ADMIN);
242
violations = validator.validate(adminUser);
243
// Validates: username, email, phoneNumber, adminToken
244
```
245
246
### Locale Resolver SPI
247
248
Resolve locale dynamically for message interpolation.
249
250
```java { .api }
251
package org.hibernate.validator.spi.messageinterpolation;
252
253
import java.util.Locale;
254
255
/**
256
* Resolves locale for message interpolation.
257
* Allows dynamic locale selection based on context.
258
*
259
* @since 6.1.1
260
*/
261
@Incubating
262
interface LocaleResolver {
263
/**
264
* Resolve locale for given context.
265
*
266
* @param context locale resolution context
267
* @return resolved locale
268
*/
269
Locale resolve(LocaleResolverContext context);
270
}
271
272
/**
273
* Context for locale resolution.
274
*
275
* @since 6.1.1
276
*/
277
@Incubating
278
interface LocaleResolverContext {
279
/**
280
* Get default locale configured for validator factory.
281
*
282
* @return default locale
283
*/
284
Locale getDefaultLocale();
285
286
/**
287
* Get set of supported locales configured for validator factory.
288
*
289
* @return supported locales
290
*/
291
java.util.Set<Locale> getSupportedLocales();
292
}
293
```
294
295
**Usage Example:**
296
297
```java
298
import org.hibernate.validator.spi.messageinterpolation.LocaleResolver;
299
import org.hibernate.validator.spi.messageinterpolation.LocaleResolverContext;
300
import org.hibernate.validator.HibernateValidator;
301
import jakarta.validation.*;
302
import java.util.*;
303
304
/**
305
* Locale resolver that uses thread-local context.
306
*/
307
class ThreadLocalLocaleResolver implements LocaleResolver {
308
309
private static final ThreadLocal<Locale> LOCALE_HOLDER = new ThreadLocal<>();
310
311
public static void setLocale(Locale locale) {
312
LOCALE_HOLDER.set(locale);
313
}
314
315
public static void clearLocale() {
316
LOCALE_HOLDER.remove();
317
}
318
319
@Override
320
public Locale resolve(LocaleResolverContext context) {
321
Locale locale = LOCALE_HOLDER.get();
322
323
if (locale != null && context.getSupportedLocales().contains(locale)) {
324
return locale;
325
}
326
327
// Fall back to default locale
328
return context.getDefaultLocale();
329
}
330
}
331
332
// Configure validator
333
LocaleResolver localeResolver = new ThreadLocalLocaleResolver();
334
335
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
336
.configure()
337
.locales(Locale.US, Locale.FRANCE, Locale.GERMANY)
338
.defaultLocale(Locale.US)
339
.localeResolver(localeResolver)
340
.buildValidatorFactory();
341
342
Validator validator = factory.getValidator();
343
344
// Use with different locales per thread
345
ThreadLocalLocaleResolver.setLocale(Locale.FRANCE);
346
Set<ConstraintViolation<User>> violations = validator.validate(user);
347
// Messages interpolated in French
348
349
ThreadLocalLocaleResolver.setLocale(Locale.GERMANY);
350
violations = validator.validate(user);
351
// Messages interpolated in German
352
353
ThreadLocalLocaleResolver.clearLocale();
354
```
355
356
### Property Node Name Provider SPI
357
358
Resolve property node names dynamically in validation paths.
359
360
```java { .api }
361
package org.hibernate.validator.spi.nodenameprovider;
362
363
/**
364
* Resolves property node name when creating property path.
365
* Allows customizing how property names appear in constraint violations.
366
*
367
* @since 6.1.0
368
*/
369
@Incubating
370
interface PropertyNodeNameProvider {
371
/**
372
* Get name for property.
373
*
374
* @param property property to resolve name for
375
* @return property name
376
*/
377
String getName(Property property);
378
}
379
380
/**
381
* Represents a property for name resolution.
382
*/
383
@Incubating
384
interface Property {
385
/**
386
* Get property name.
387
*
388
* @return property name
389
*/
390
String getName();
391
}
392
393
/**
394
* Represents JavaBean property with additional metadata.
395
*/
396
@Incubating
397
interface JavaBeanProperty extends Property {
398
/**
399
* Get declaring class.
400
*
401
* @return class declaring this property
402
*/
403
Class<?> getDeclaringClass();
404
405
/**
406
* Get property type.
407
*
408
* @return property type
409
*/
410
Class<?> getType();
411
}
412
```
413
414
**Usage Example:**
415
416
```java
417
import org.hibernate.validator.spi.nodenameprovider.*;
418
import org.hibernate.validator.HibernateValidator;
419
import jakarta.validation.*;
420
import java.lang.reflect.Field;
421
422
/**
423
* Property node name provider using @JsonProperty annotation names.
424
*/
425
class JsonPropertyNodeNameProvider implements PropertyNodeNameProvider {
426
427
@Override
428
public String getName(Property property) {
429
if (property instanceof JavaBeanProperty) {
430
JavaBeanProperty javaProperty = (JavaBeanProperty) property;
431
432
// Try to find @JsonProperty annotation
433
try {
434
Field field = javaProperty.getDeclaringClass()
435
.getDeclaredField(property.getName());
436
437
JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
438
if (jsonProperty != null && !jsonProperty.value().isEmpty()) {
439
return jsonProperty.value();
440
}
441
} catch (NoSuchFieldException e) {
442
// Fall through to default name
443
}
444
}
445
446
// Use default property name
447
return property.getName();
448
}
449
}
450
451
// Example annotation
452
@interface JsonProperty {
453
String value();
454
}
455
456
// Example entity
457
class ApiRequest {
458
@JsonProperty("user_name")
459
@NotBlank
460
private String userName;
461
462
@JsonProperty("email_address")
463
464
private String emailAddress;
465
466
// getters/setters...
467
}
468
469
// Configure validator
470
PropertyNodeNameProvider nameProvider = new JsonPropertyNodeNameProvider();
471
472
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
473
.configure()
474
.propertyNodeNameProvider(nameProvider)
475
.buildValidatorFactory();
476
477
Validator validator = factory.getValidator();
478
479
ApiRequest request = new ApiRequest();
480
Set<ConstraintViolation<ApiRequest>> violations = validator.validate(request);
481
482
for (ConstraintViolation<ApiRequest> violation : violations) {
483
// Property path uses JSON names: "user_name", "email_address"
484
System.out.println(violation.getPropertyPath());
485
}
486
```
487
488
### Getter Property Selection Strategy SPI
489
490
Define strategy for detecting JavaBean getters.
491
492
```java { .api }
493
package org.hibernate.validator.spi.properties;
494
495
import java.util.List;
496
import java.util.Optional;
497
498
/**
499
* Defines strategy for detecting getters of a bean.
500
* Determines which methods are considered JavaBean getters.
501
*
502
* @since 6.1.0
503
*/
504
@Incubating
505
interface GetterPropertySelectionStrategy {
506
/**
507
* Determine if method is a getter and return property name.
508
*
509
* @param executable method to check
510
* @return Optional containing property name if method is getter, empty otherwise
511
*/
512
Optional<String> getProperty(ConstrainableExecutable executable);
513
514
/**
515
* Get possible getter method names for property name.
516
*
517
* @param propertyName property name
518
* @return list of possible getter method names
519
*/
520
List<String> getGetterMethodNameCandidates(String propertyName);
521
}
522
523
/**
524
* Represents an executable (method or constructor) that can be constrained.
525
*/
526
@Incubating
527
interface ConstrainableExecutable {
528
/**
529
* Get method/constructor name.
530
*
531
* @return executable name
532
*/
533
String getName();
534
535
/**
536
* Get return type.
537
*
538
* @return return type
539
*/
540
Class<?> getReturnType();
541
542
/**
543
* Get parameter types.
544
*
545
* @return parameter types
546
*/
547
Class<?>[] getParameterTypes();
548
549
/**
550
* Check if this is a method.
551
*
552
* @return true if method, false if constructor
553
*/
554
boolean isMethod();
555
}
556
```
557
558
**Usage Example:**
559
560
```java
561
import org.hibernate.validator.spi.properties.*;
562
import org.hibernate.validator.HibernateValidator;
563
import jakarta.validation.*;
564
import java.util.*;
565
566
/**
567
* Custom getter selection strategy supporting "read" prefix.
568
* Recognizes: get, is, has, read
569
*/
570
class ExtendedGetterSelectionStrategy implements GetterPropertySelectionStrategy {
571
572
@Override
573
public Optional<String> getProperty(ConstrainableExecutable executable) {
574
if (!executable.isMethod()) {
575
return Optional.empty();
576
}
577
578
String methodName = executable.getName();
579
Class<?> returnType = executable.getReturnType();
580
Class<?>[] paramTypes = executable.getParameterTypes();
581
582
// Must have no parameters
583
if (paramTypes.length != 0) {
584
return Optional.empty();
585
}
586
587
// Must have return value
588
if (returnType == void.class) {
589
return Optional.empty();
590
}
591
592
// Check standard patterns
593
if (methodName.startsWith("get") && methodName.length() > 3) {
594
return Optional.of(decapitalize(methodName.substring(3)));
595
}
596
597
if (methodName.startsWith("is") && methodName.length() > 2 &&
598
(returnType == boolean.class || returnType == Boolean.class)) {
599
return Optional.of(decapitalize(methodName.substring(2)));
600
}
601
602
if (methodName.startsWith("has") && methodName.length() > 3) {
603
return Optional.of(decapitalize(methodName.substring(3)));
604
}
605
606
// Custom: support "read" prefix
607
if (methodName.startsWith("read") && methodName.length() > 4) {
608
return Optional.of(decapitalize(methodName.substring(4)));
609
}
610
611
return Optional.empty();
612
}
613
614
@Override
615
public List<String> getGetterMethodNameCandidates(String propertyName) {
616
String capitalized = capitalize(propertyName);
617
618
return Arrays.asList(
619
"get" + capitalized,
620
"is" + capitalized,
621
"has" + capitalized,
622
"read" + capitalized // Custom
623
);
624
}
625
626
private String decapitalize(String string) {
627
if (string.isEmpty()) {
628
return string;
629
}
630
return Character.toLowerCase(string.charAt(0)) + string.substring(1);
631
}
632
633
private String capitalize(String string) {
634
if (string.isEmpty()) {
635
return string;
636
}
637
return Character.toUpperCase(string.charAt(0)) + string.substring(1);
638
}
639
}
640
641
// Configure validator
642
GetterPropertySelectionStrategy strategy = new ExtendedGetterSelectionStrategy();
643
644
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
645
.configure()
646
.getterPropertySelectionStrategy(strategy)
647
.buildValidatorFactory();
648
649
// Now methods like readProperty() are recognized as getters
650
class Document {
651
private String content;
652
653
@NotBlank // Will validate the property via readContent()
654
public String readContent() {
655
return content;
656
}
657
}
658
```
659
660
### Resource Bundle Locator SPI
661
662
SPI for custom resource bundle loading strategies.
663
664
```java { .api }
665
package org.hibernate.validator.spi.resourceloading;
666
667
import java.util.Locale;
668
import java.util.ResourceBundle;
669
670
/**
671
* Locates resource bundles for message interpolation.
672
* Implement to provide custom resource bundle loading.
673
*/
674
interface ResourceBundleLocator {
675
/**
676
* Get resource bundle for specified locale.
677
*
678
* @param locale locale for bundle
679
* @return resource bundle
680
*/
681
ResourceBundle getResourceBundle(Locale locale);
682
}
683
```
684
685
**Usage Example:** See [Resource Bundle Loading](./resource-loading.md) for detailed examples.
686
687
### Script Evaluator Factory SPI
688
689
Factory for creating script evaluators for @ScriptAssert constraints.
690
691
```java { .api }
692
package org.hibernate.validator.spi.scripting;
693
694
/**
695
* Factory for creating ScriptEvaluators.
696
* Implement to provide custom script evaluation.
697
*
698
* @since 6.0.3
699
*/
700
@Incubating
701
interface ScriptEvaluatorFactory {
702
/**
703
* Get script evaluator for specified language.
704
*
705
* @param languageName script language name
706
* @return script evaluator
707
* @throws ScriptEvaluatorNotFoundException if language not supported
708
*/
709
ScriptEvaluator getScriptEvaluatorByLanguageName(String languageName);
710
711
/**
712
* Clear factory state (caches, etc.).
713
*/
714
void clear();
715
}
716
717
/**
718
* Evaluates script expressions.
719
*/
720
@Incubating
721
interface ScriptEvaluator {
722
/**
723
* Evaluate script expression.
724
*
725
* @param script script to evaluate
726
* @param bindings variables available in script
727
* @return evaluation result
728
* @throws ScriptEvaluationException if evaluation fails
729
*/
730
Object evaluate(String script, java.util.Map<String, Object> bindings)
731
throws ScriptEvaluationException;
732
}
733
734
/**
735
* Base class for script evaluator factories with caching.
736
*/
737
@Incubating
738
abstract class AbstractCachingScriptEvaluatorFactory implements ScriptEvaluatorFactory {
739
/**
740
* Get or create cached script evaluator.
741
*
742
* @param languageName language name
743
* @return cached script evaluator
744
*/
745
protected abstract ScriptEvaluator createScriptEvaluator(String languageName);
746
747
@Override
748
public final ScriptEvaluator getScriptEvaluatorByLanguageName(String languageName) {
749
// Caching implementation
750
}
751
752
@Override
753
public void clear() {
754
// Clear cache
755
}
756
}
757
758
/**
759
* ScriptEvaluator using javax.script.ScriptEngine.
760
*/
761
@Incubating
762
class ScriptEngineScriptEvaluator implements ScriptEvaluator {
763
/**
764
* Create evaluator for script engine.
765
*
766
* @param scriptEngine JSR 223 script engine
767
*/
768
ScriptEngineScriptEvaluator(javax.script.ScriptEngine scriptEngine);
769
770
@Override
771
Object evaluate(String script, java.util.Map<String, Object> bindings);
772
}
773
774
// Exception classes
775
class ScriptEvaluationException extends RuntimeException {
776
ScriptEvaluationException(String message);
777
ScriptEvaluationException(String message, Throwable cause);
778
}
779
780
class ScriptEvaluatorNotFoundException extends RuntimeException {
781
ScriptEvaluatorNotFoundException(String message);
782
}
783
```
784
785
**Usage Example:**
786
787
```java
788
import org.hibernate.validator.spi.scripting.*;
789
import org.hibernate.validator.HibernateValidator;
790
import jakarta.validation.*;
791
import javax.script.*;
792
import java.util.*;
793
794
/**
795
* Custom script evaluator factory with security restrictions.
796
*/
797
class SecureScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {
798
799
@Override
800
protected ScriptEvaluator createScriptEvaluator(String languageName) {
801
if (!"javascript".equalsIgnoreCase(languageName)) {
802
throw new ScriptEvaluatorNotFoundException(
803
"Only JavaScript is supported");
804
}
805
806
// Create sandboxed JavaScript engine
807
ScriptEngineManager manager = new ScriptEngineManager();
808
ScriptEngine engine = manager.getEngineByName("javascript");
809
810
if (engine == null) {
811
throw new ScriptEvaluatorNotFoundException(
812
"JavaScript engine not available");
813
}
814
815
// Apply security restrictions
816
configureSecurityRestrictions(engine);
817
818
return new ScriptEngineScriptEvaluator(engine);
819
}
820
821
private void configureSecurityRestrictions(ScriptEngine engine) {
822
// Restrict access to dangerous classes/methods
823
// Implementation depends on script engine
824
}
825
}
826
827
// Configure validator
828
ScriptEvaluatorFactory scriptFactory = new SecureScriptEvaluatorFactory();
829
830
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
831
.configure()
832
.scriptEvaluatorFactory(scriptFactory)
833
.buildValidatorFactory();
834
835
// Use @ScriptAssert constraints
836
@ScriptAssert(
837
lang = "javascript",
838
script = "_this.endDate > _this.startDate",
839
message = "End date must be after start date"
840
)
841
class DateRange {
842
private Date startDate;
843
private Date endDate;
844
// getters/setters...
845
}
846
```
847
848
### Parameter Name Provider Implementation
849
850
Hibernate Validator provides an alternative parameter name provider implementation using the ParaNamer library.
851
852
```java { .api }
853
package org.hibernate.validator.parameternameprovider;
854
855
import jakarta.validation.ParameterNameProvider;
856
import com.thoughtworks.paranamer.Paranamer;
857
import java.lang.reflect.Constructor;
858
import java.lang.reflect.Method;
859
import java.util.List;
860
861
/**
862
* ParameterNameProvider implementation backed by the ParaNamer library.
863
* Extracts parameter names from debug information or other sources.
864
*
865
* The ParaNamer implementation can be customized via constructor.
866
* By default uses AdaptiveParanamer wrapped in CachingParanamer.
867
* Falls back to default parameter names if ParaNamer cannot retrieve names.
868
*
869
* Requires ParaNamer library on classpath.
870
*
871
* @see <a href="http://paranamer.codehaus.org/">ParaNamer web site</a>
872
*/
873
class ParanamerParameterNameProvider implements ParameterNameProvider {
874
/**
875
* Create provider with default Paranamer (AdaptiveParanamer with caching).
876
*/
877
ParanamerParameterNameProvider();
878
879
/**
880
* Create provider with custom Paranamer implementation.
881
*
882
* @param paranamer custom paranamer implementation, null for default
883
*/
884
ParanamerParameterNameProvider(Paranamer paranamer);
885
886
/**
887
* Get parameter names for constructor.
888
* Falls back to default names if ParaNamer cannot retrieve them.
889
*
890
* @param constructor the constructor
891
* @return list of parameter names
892
*/
893
List<String> getParameterNames(Constructor<?> constructor);
894
895
/**
896
* Get parameter names for method.
897
* Falls back to default names if ParaNamer cannot retrieve them.
898
*
899
* @param method the method
900
* @return list of parameter names
901
*/
902
List<String> getParameterNames(Method method);
903
}
904
```
905
906
**Usage Example:**
907
908
```java
909
import org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider;
910
import jakarta.validation.*;
911
import jakarta.validation.constraints.*;
912
import jakarta.validation.executable.*;
913
914
// 1. Configure validator factory with ParanamerParameterNameProvider
915
ValidatorFactory factory = Validation.byDefaultProvider()
916
.configure()
917
.parameterNameProvider(new ParanamerParameterNameProvider())
918
.buildValidatorFactory();
919
920
Validator validator = factory.getValidator();
921
ExecutableValidator executableValidator = validator.forExecutables();
922
923
// 2. Bean with validated method
924
class UserService {
925
/**
926
* Create new user with validation.
927
* When compiled with debug information (-g flag), parameter names
928
* are preserved and used in validation messages.
929
*/
930
public User createUser(
931
@NotNull @Size(min = 3, max = 50) String username,
932
@NotNull @Email String email,
933
@Min(18) int age
934
) {
935
return new User(username, email, age);
936
}
937
}
938
939
// 3. Validate method parameters
940
UserService service = new UserService();
941
Method method = UserService.class.getMethod("createUser", String.class, String.class, int.class);
942
943
Object[] params = {"jo", "invalid", 15}; // All parameters invalid
944
945
Set<ConstraintViolation<UserService>> violations = executableValidator.validateParameters(
946
service,
947
method,
948
params
949
);
950
951
// With ParaNamer, violation messages use actual parameter names:
952
// - "createUser.username: size must be between 3 and 50"
953
// - "createUser.email: must be a well-formed email address"
954
// - "createUser.age: must be greater than or equal to 18"
955
//
956
// Without ParaNamer, generic names would be used:
957
// - "createUser.arg0: size must be between 3 and 50"
958
// - "createUser.arg1: must be a well-formed email address"
959
// - "createUser.arg2: must be greater than or equal to 18"
960
```
961
962
## Complete SPI Integration Example
963
964
```java
965
import org.hibernate.validator.*;
966
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
967
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
968
import org.hibernate.validator.spi.messageinterpolation.*;
969
import org.hibernate.validator.spi.nodenameprovider.*;
970
import org.hibernate.validator.spi.properties.*;
971
import org.hibernate.validator.spi.scripting.*;
972
import jakarta.validation.*;
973
import java.util.*;
974
975
/**
976
* Complete custom validation configuration using all SPIs.
977
*/
978
class CustomValidationConfiguration {
979
980
public ValidatorFactory createValidatorFactory() {
981
// Create SPI implementations
982
ConstraintMappingContributor mappingContributor = new CustomMappingContributor();
983
LocaleResolver localeResolver = new ThreadLocalLocaleResolver();
984
PropertyNodeNameProvider nodeNameProvider = new JsonPropertyNodeNameProvider();
985
GetterPropertySelectionStrategy getterStrategy = new ExtendedGetterSelectionStrategy();
986
ScriptEvaluatorFactory scriptFactory = new SecureScriptEvaluatorFactory();
987
988
// Configure validator factory
989
return Validation.byProvider(HibernateValidator.class)
990
.configure()
991
// Add constraint mapping contributor
992
.addProperty(
993
BaseHibernateValidatorConfiguration.CONSTRAINT_MAPPING_CONTRIBUTORS,
994
CustomMappingContributor.class.getName()
995
)
996
// Configure locale resolution
997
.locales(Locale.US, Locale.FRANCE, Locale.GERMANY)
998
.defaultLocale(Locale.US)
999
.localeResolver(localeResolver)
1000
// Configure property name resolution
1001
.propertyNodeNameProvider(nodeNameProvider)
1002
// Configure getter detection
1003
.getterPropertySelectionStrategy(getterStrategy)
1004
// Configure script evaluation
1005
.scriptEvaluatorFactory(scriptFactory)
1006
// Other settings
1007
.failFast(false)
1008
.constraintExpressionLanguageFeatureLevel(
1009
ExpressionLanguageFeatureLevel.BEAN_PROPERTIES)
1010
.buildValidatorFactory();
1011
}
1012
}
1013
```
1014