0
# Message Interpolation
1
2
Advanced message interpolation with Expression Language support, configurable feature levels for security, custom locale resolution, resource bundle aggregation, and parameter-only interpolation for EL-free environments.
3
4
## Capabilities
5
6
### Resource Bundle Message Interpolator
7
8
Resource bundle-backed message interpolator with full Expression Language support.
9
10
```java { .api }
11
package org.hibernate.validator.messageinterpolation;
12
13
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
14
import java.util.Locale;
15
import java.util.Set;
16
17
/**
18
* Resource bundle-backed message interpolator with Expression Language support.
19
* Default message interpolator for Hibernate Validator.
20
*/
21
class ResourceBundleMessageInterpolator extends AbstractMessageInterpolator {
22
/**
23
* Create interpolator using default resource bundle ("ValidationMessages").
24
*/
25
ResourceBundleMessageInterpolator();
26
27
/**
28
* Create interpolator with custom user resource bundle locator.
29
*
30
* @param userResourceBundleLocator locator for user resource bundles
31
*/
32
ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator);
33
34
/**
35
* Create interpolator with user and contributor resource bundle locators.
36
*
37
* @param userResourceBundleLocator locator for user resource bundles
38
* @param contributorResourceBundleLocator locator for contributor resource bundles
39
*/
40
ResourceBundleMessageInterpolator(
41
ResourceBundleLocator userResourceBundleLocator,
42
ResourceBundleLocator contributorResourceBundleLocator);
43
44
/**
45
* Create interpolator with user and contributor locators and caching control.
46
*
47
* @param userResourceBundleLocator locator for user resource bundles
48
* @param contributorResourceBundleLocator locator for contributor resource bundles
49
* @param cachingEnabled whether to enable message caching
50
*/
51
ResourceBundleMessageInterpolator(
52
ResourceBundleLocator userResourceBundleLocator,
53
ResourceBundleLocator contributorResourceBundleLocator,
54
boolean cachingEnabled);
55
56
/**
57
* Create interpolator with locale preloading support.
58
*
59
* @param userResourceBundleLocator locator for user resource bundles
60
* @param contributorResourceBundleLocator locator for contributor resource bundles
61
* @param cachingEnabled whether to enable message caching
62
* @param preloadResourceBundles whether to preload resource bundles for all locales
63
* @since 6.1.1
64
*/
65
@Incubating
66
ResourceBundleMessageInterpolator(
67
ResourceBundleLocator userResourceBundleLocator,
68
ResourceBundleLocator contributorResourceBundleLocator,
69
boolean cachingEnabled,
70
boolean preloadResourceBundles);
71
72
/**
73
* Create interpolator with locale support and preloading.
74
*
75
* @param userResourceBundleLocator locator for user resource bundles
76
* @param contributorResourceBundleLocator locator for contributor resource bundles
77
* @param cachingEnabled whether to enable message caching
78
* @param preloadResourceBundles whether to preload resource bundles
79
* @param defaultLocale default locale for message interpolation
80
* @param supportedLocales supported locales
81
* @since 6.1.1
82
*/
83
@Incubating
84
ResourceBundleMessageInterpolator(
85
ResourceBundleLocator userResourceBundleLocator,
86
ResourceBundleLocator contributorResourceBundleLocator,
87
boolean cachingEnabled,
88
boolean preloadResourceBundles,
89
Locale defaultLocale,
90
Set<Locale> supportedLocales);
91
}
92
```
93
94
**Usage Example:**
95
96
```java
97
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
98
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
99
import org.hibernate.validator.resourceloading.AggregateResourceBundleLocator;
100
import jakarta.validation.*;
101
import java.util.*;
102
103
// Create custom resource bundle locators
104
ResourceBundleLocator userLocator = new PlatformResourceBundleLocator("MyValidationMessages");
105
ResourceBundleLocator contributorLocator = new PlatformResourceBundleLocator("ContributorMessages");
106
107
// Create message interpolator with custom locators
108
ResourceBundleMessageInterpolator interpolator = new ResourceBundleMessageInterpolator(
109
userLocator,
110
contributorLocator,
111
true, // Enable caching
112
true, // Preload bundles
113
Locale.US,
114
Set.of(Locale.US, Locale.UK, Locale.FRANCE)
115
);
116
117
// Configure validator to use custom interpolator
118
ValidatorFactory factory = Validation.byDefaultProvider()
119
.configure()
120
.messageInterpolator(interpolator)
121
.buildValidatorFactory();
122
```
123
124
### Parameter Message Interpolator
125
126
Message interpolator without Expression Language support (parameter values only).
127
128
```java { .api }
129
package org.hibernate.validator.messageinterpolation;
130
131
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
132
import java.util.Locale;
133
import java.util.Set;
134
135
/**
136
* Resource bundle message interpolator without EL support.
137
* Only interpolates parameter values, providing better security
138
* by avoiding expression evaluation. Use when EL features are not needed.
139
*
140
* @since 5.2
141
*/
142
class ParameterMessageInterpolator extends AbstractMessageInterpolator {
143
/**
144
* Create interpolator using default resource bundle.
145
*/
146
ParameterMessageInterpolator();
147
148
/**
149
* Create interpolator with custom resource bundle locator.
150
*
151
* @param userResourceBundleLocator locator for user resource bundles
152
*/
153
ParameterMessageInterpolator(ResourceBundleLocator userResourceBundleLocator);
154
155
/**
156
* Create interpolator with user and contributor locators.
157
*
158
* @param userResourceBundleLocator locator for user resource bundles
159
* @param contributorResourceBundleLocator locator for contributor resource bundles
160
*/
161
ParameterMessageInterpolator(
162
ResourceBundleLocator userResourceBundleLocator,
163
ResourceBundleLocator contributorResourceBundleLocator);
164
165
/**
166
* Create interpolator with caching control.
167
*
168
* @param userResourceBundleLocator locator for user resource bundles
169
* @param contributorResourceBundleLocator locator for contributor resource bundles
170
* @param cachingEnabled whether to enable message caching
171
*/
172
ParameterMessageInterpolator(
173
ResourceBundleLocator userResourceBundleLocator,
174
ResourceBundleLocator contributorResourceBundleLocator,
175
boolean cachingEnabled);
176
177
/**
178
* Create interpolator with locale support.
179
*
180
* @param userResourceBundleLocator locator for user resource bundles
181
* @param contributorResourceBundleLocator locator for contributor resource bundles
182
* @param cachingEnabled whether to enable message caching
183
* @param preloadResourceBundles whether to preload resource bundles
184
* @param defaultLocale default locale
185
* @param supportedLocales supported locales
186
* @since 6.1.1
187
*/
188
@Incubating
189
ParameterMessageInterpolator(
190
ResourceBundleLocator userResourceBundleLocator,
191
ResourceBundleLocator contributorResourceBundleLocator,
192
boolean cachingEnabled,
193
boolean preloadResourceBundles,
194
Locale defaultLocale,
195
Set<Locale> supportedLocales);
196
}
197
```
198
199
**Usage Example:**
200
201
```java
202
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
203
204
// Use parameter-only interpolator for security
205
ParameterMessageInterpolator interpolator = new ParameterMessageInterpolator();
206
207
ValidatorFactory factory = Validation.byDefaultProvider()
208
.configure()
209
.messageInterpolator(interpolator)
210
.buildValidatorFactory();
211
212
// Now only parameter substitution works:
213
// "{min} <= value <= {max}" -> "0 <= value <= 100"
214
// EL expressions like "${formatter.format(...)}" will NOT be evaluated
215
```
216
217
### Abstract Message Interpolator
218
219
Base class for message interpolators providing common functionality.
220
221
```java { .api }
222
package org.hibernate.validator.messageinterpolation;
223
224
import jakarta.validation.MessageInterpolator;
225
import java.util.Locale;
226
227
/**
228
* Base class for message interpolators.
229
* Provides common functionality and template method for customization.
230
*/
231
abstract class AbstractMessageInterpolator implements MessageInterpolator {
232
/**
233
* Interpolate message with given context.
234
*
235
* @param message message template
236
* @param context interpolation context
237
* @return interpolated message
238
*/
239
@Override
240
String interpolate(String message, Context context);
241
242
/**
243
* Interpolate message with given context and locale.
244
*
245
* @param message message template
246
* @param context interpolation context
247
* @param locale locale for interpolation
248
* @return interpolated message
249
*/
250
@Override
251
String interpolate(String message, Context context, Locale locale);
252
}
253
```
254
255
### Expression Language Feature Level
256
257
Configure Expression Language features for security control.
258
259
```java { .api }
260
package org.hibernate.validator.messageinterpolation;
261
262
/**
263
* Expression Language feature level for controlling which EL features
264
* are available for message interpolation. Provides security control
265
* over EL expression evaluation.
266
*
267
* @since 6.2
268
*/
269
@Incubating
270
enum ExpressionLanguageFeatureLevel {
271
/**
272
* Context-dependent default level.
273
* For constraints: resolves to BEAN_PROPERTIES (spec-compliant).
274
* For custom violations: resolves to VARIABLES (safest for user input).
275
*/
276
DEFAULT,
277
278
/**
279
* No EL interpolation.
280
* Only parameter substitution (like {min}, {max}) is performed.
281
* Safest option, equivalent to ParameterMessageInterpolator.
282
*/
283
NONE,
284
285
/**
286
* Only injected variables, formatter, and ResourceBundles.
287
* EL expressions can access:
288
* - Variables added via addExpressionVariable()
289
* - formatter object for formatting
290
* - ResourceBundle messages
291
* No access to bean properties or methods.
292
*/
293
VARIABLES,
294
295
/**
296
* Variables plus bean properties (specification-compliant minimum).
297
* EL expressions can access:
298
* - All VARIABLES features
299
* - Bean property getters (e.g., ${validatedValue.property})
300
* No method execution allowed (except property getters).
301
* This is the Jakarta Validation specification minimum.
302
*/
303
BEAN_PROPERTIES,
304
305
/**
306
* Bean properties plus method execution (SECURITY RISK!).
307
* EL expressions can access:
308
* - All BEAN_PROPERTIES features
309
* - Arbitrary method execution (e.g., ${object.method()})
310
* WARNING: This is a security risk if user input can influence messages.
311
* Only use in trusted environments.
312
*/
313
BEAN_METHODS;
314
315
/**
316
* Parse feature level from string representation.
317
*
318
* @param expressionLanguageFeatureLevelString string representation
319
* @return parsed feature level
320
* @throws IllegalArgumentException if string is invalid
321
*/
322
static ExpressionLanguageFeatureLevel of(String expressionLanguageFeatureLevelString);
323
324
/**
325
* Interpret DEFAULT for constraint messages.
326
* Returns BEAN_PROPERTIES (spec-compliant) for DEFAULT, otherwise returns input.
327
*
328
* @param expressionLanguageFeatureLevel feature level to interpret
329
* @return interpreted feature level
330
*/
331
static ExpressionLanguageFeatureLevel interpretDefaultForConstraints(
332
ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel);
333
334
/**
335
* Interpret DEFAULT for custom violation messages.
336
* Returns VARIABLES (safest for user input) for DEFAULT, otherwise returns input.
337
*
338
* @param expressionLanguageFeatureLevel feature level to interpret
339
* @return interpreted feature level
340
*/
341
static ExpressionLanguageFeatureLevel interpretDefaultForCustomViolations(
342
ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel);
343
}
344
```
345
346
**Usage Example:**
347
348
```java
349
import org.hibernate.validator.HibernateValidator;
350
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
351
import jakarta.validation.Validation;
352
353
// Configure EL feature level for security
354
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
355
.configure()
356
// Set EL level for static constraint messages
357
.constraintExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel.BEAN_PROPERTIES)
358
// Set EL level for custom violations (more restrictive for user input)
359
.customViolationExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel.VARIABLES)
360
.buildValidatorFactory();
361
362
// Feature level comparison:
363
// NONE: "{min} <= value <= {max}" -> "0 <= value <= 100"
364
// VARIABLES: "${myVar} is invalid" -> "custom value is invalid"
365
// BEAN_PROPERTIES: "${validatedValue.length()} chars" -> "5 chars"
366
// BEAN_METHODS: "${validatedValue.toUpperCase()}" -> "HELLO" (SECURITY RISK!)
367
```
368
369
### Hibernate Message Interpolator Context
370
371
Hibernate-specific extension to message interpolator context.
372
373
```java { .api }
374
package org.hibernate.validator.messageinterpolation;
375
376
import jakarta.validation.MessageInterpolator;
377
378
/**
379
* Hibernate-specific extension to MessageInterpolator.Context.
380
* Provides additional context information for message interpolation.
381
*/
382
interface HibernateMessageInterpolatorContext extends MessageInterpolator.Context {
383
/**
384
* Get expression language variables added during validation.
385
* Variables are added via HibernateConstraintValidatorContext.addExpressionVariable().
386
*
387
* @return map of expression language variables
388
*/
389
java.util.Map<String, Object> getExpressionVariables();
390
391
/**
392
* Get the root bean being validated.
393
*
394
* @return root bean or null if not available
395
*/
396
Object getRootBean();
397
398
/**
399
* Get message parameters added during validation.
400
* Parameters are added via HibernateConstraintValidatorContext.addMessageParameter().
401
*
402
* @return map of message parameters
403
*/
404
java.util.Map<String, Object> getMessageParameters();
405
}
406
```
407
408
**Usage Example:**
409
410
```java
411
import org.hibernate.validator.messageinterpolation.HibernateMessageInterpolatorContext;
412
import jakarta.validation.MessageInterpolator;
413
import jakarta.validation.metadata.ConstraintDescriptor;
414
import java.util.Locale;
415
import java.util.Map;
416
417
// Custom message interpolator using Hibernate context
418
class CustomMessageInterpolator implements MessageInterpolator {
419
420
@Override
421
public String interpolate(String messageTemplate, Context context) {
422
return interpolate(messageTemplate, context, Locale.getDefault());
423
}
424
425
@Override
426
public String interpolate(String messageTemplate, Context context, Locale locale) {
427
// Unwrap to Hibernate context
428
HibernateMessageInterpolatorContext hibernateContext =
429
context.unwrap(HibernateMessageInterpolatorContext.class);
430
431
// Access additional context information
432
Map<String, Object> messageParams = hibernateContext.getMessageParameters();
433
Map<String, Object> expressionVars = hibernateContext.getExpressionVariables();
434
Object rootBean = hibernateContext.getRootBean();
435
436
// Perform custom interpolation
437
String message = messageTemplate;
438
439
// Replace message parameters {paramName}
440
for (Map.Entry<String, Object> entry : messageParams.entrySet()) {
441
message = message.replace("{" + entry.getKey() + "}", String.valueOf(entry.getValue()));
442
}
443
444
// Process expression variables if needed
445
// ...
446
447
return message;
448
}
449
}
450
```
451
452
## Message Interpolation Examples
453
454
### Basic Message Templates
455
456
```java
457
// ValidationMessages.properties
458
javax.validation.constraints.NotNull.message=must not be null
459
javax.validation.constraints.Size.message=size must be between {min} and {max}
460
org.hibernate.validator.constraints.Length.message=length must be between {min} and {max}
461
462
// Custom messages
463
user.email.invalid=Email address is invalid
464
user.age.range=Age must be between {min} and {max}, but was {value}
465
```
466
467
### Expression Language in Messages
468
469
```java
470
// Using formatter in message templates
471
@Size(min = 2, max = 10, message = "size is ${validatedValue.length()}, must be between {min} and {max}")
472
private String name;
473
474
// Using validated value
475
@Pattern(regexp = "[A-Z]+", message = "${validatedValue} must contain only uppercase letters")
476
private String code;
477
478
// Complex expressions with formatter
479
@DecimalMin(value = "0.0", message = "Value ${formatter.format('%1$.2f', validatedValue)} must be positive")
480
private BigDecimal amount;
481
```
482
483
### Custom Message Parameters
484
485
```java
486
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
487
import jakarta.validation.*;
488
489
class PriceRangeValidator implements ConstraintValidator<PriceRange, BigDecimal> {
490
491
private BigDecimal min;
492
private BigDecimal max;
493
private String currency;
494
495
@Override
496
public void initialize(PriceRange constraintAnnotation) {
497
this.min = constraintAnnotation.min();
498
this.max = constraintAnnotation.max();
499
this.currency = constraintAnnotation.currency();
500
}
501
502
@Override
503
public boolean isValid(BigDecimal value, ConstraintValidatorContext context) {
504
if (value == null) {
505
return true;
506
}
507
508
if (value.compareTo(min) < 0 || value.compareTo(max) > 0) {
509
HibernateConstraintValidatorContext hibernateContext =
510
context.unwrap(HibernateConstraintValidatorContext.class);
511
512
context.disableDefaultConstraintViolation();
513
514
hibernateContext
515
.addMessageParameter("min", min)
516
.addMessageParameter("max", max)
517
.addMessageParameter("currency", currency)
518
.addMessageParameter("value", value)
519
.buildConstraintViolationWithTemplate(
520
"Price {value} {currency} is out of range [{min}, {max}] {currency}")
521
.addConstraintViolation();
522
523
return false;
524
}
525
526
return true;
527
}
528
}
529
530
// Message output: "Price 150.00 USD is out of range [10.00, 100.00] USD"
531
```
532
533
### Expression Language Variables
534
535
```java
536
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
537
538
class StockValidator implements ConstraintValidator<InStock, Integer> {
539
540
@Override
541
public boolean isValid(Integer quantity, ConstraintValidatorContext context) {
542
if (quantity == null) {
543
return true;
544
}
545
546
int available = getAvailableStock(); // Get from inventory system
547
548
if (quantity > available) {
549
HibernateConstraintValidatorContext hibernateContext =
550
context.unwrap(HibernateConstraintValidatorContext.class);
551
552
context.disableDefaultConstraintViolation();
553
554
hibernateContext
555
.addExpressionVariable("requested", quantity)
556
.addExpressionVariable("available", available)
557
.addExpressionVariable("shortage", quantity - available)
558
.buildConstraintViolationWithTemplate(
559
"Requested ${requested} items, only ${available} available (shortage: ${shortage})")
560
.addConstraintViolation();
561
562
return false;
563
}
564
565
return true;
566
}
567
568
private int getAvailableStock() {
569
return 50; // Example
570
}
571
}
572
573
// Message output: "Requested 75 items, only 50 available (shortage: 25)"
574
```
575
576
### Locale-Specific Messages
577
578
```java
579
// ValidationMessages.properties (default English)
580
user.age.invalid=Age must be between {min} and {max}
581
582
// ValidationMessages_fr.properties (French)
583
user.age.invalid=L'âge doit être entre {min} et {max}
584
585
// ValidationMessages_de.properties (German)
586
user.age.invalid=Das Alter muss zwischen {min} und {max} liegen
587
588
// ValidationMessages_es.properties (Spanish)
589
user.age.invalid=La edad debe estar entre {min} y {max}
590
591
// Use with locale
592
Validator validator = factory.getValidator();
593
Set<ConstraintViolation<User>> violations = validator.validate(user);
594
595
for (ConstraintViolation<User> violation : violations) {
596
// Get message in different locales
597
MessageInterpolator interpolator = factory.getMessageInterpolator();
598
599
String englishMessage = interpolator.interpolate(
600
violation.getMessageTemplate(),
601
() -> violation.getConstraintDescriptor(),
602
Locale.ENGLISH
603
);
604
605
String frenchMessage = interpolator.interpolate(
606
violation.getMessageTemplate(),
607
() -> violation.getConstraintDescriptor(),
608
Locale.FRENCH
609
);
610
611
System.out.println("EN: " + englishMessage);
612
System.out.println("FR: " + frenchMessage);
613
}
614
```
615