0
# Resource Bundle Loading
1
2
Multiple resource bundle locator implementations supporting platform resource bundles, aggregation of multiple bundles, caching, and delegation patterns for flexible message source configuration.
3
4
## Capabilities
5
6
### Platform Resource Bundle Locator
7
8
Default platform-based resource bundle locator using Java's standard ResourceBundle mechanism.
9
10
```java { .api }
11
package org.hibernate.validator.resourceloading;
12
13
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
14
import java.util.Locale;
15
import java.util.ResourceBundle;
16
17
/**
18
* Default implementation using platform's ResourceBundle mechanism.
19
* Loads resource bundles using standard Java resource bundle loading.
20
*/
21
class PlatformResourceBundleLocator implements ResourceBundleLocator {
22
/**
23
* Create locator for specified bundle name.
24
*
25
* @param bundleName resource bundle name (without .properties extension)
26
*/
27
PlatformResourceBundleLocator(String bundleName);
28
29
/**
30
* Create locator with custom class loader.
31
*
32
* @param bundleName resource bundle name
33
* @param classLoader class loader for loading bundles
34
*/
35
PlatformResourceBundleLocator(String bundleName, ClassLoader classLoader);
36
37
/**
38
* Create locator with class loader and control.
39
*
40
* @param bundleName resource bundle name
41
* @param classLoader class loader for loading bundles
42
* @param control resource bundle control for loading strategy
43
*/
44
PlatformResourceBundleLocator(
45
String bundleName,
46
ClassLoader classLoader,
47
ResourceBundle.Control control);
48
49
/**
50
* Get resource bundle for specified locale.
51
*
52
* @param locale locale for bundle
53
* @return resource bundle
54
*/
55
@Override
56
ResourceBundle getResourceBundle(Locale locale);
57
}
58
```
59
60
**Usage Example:**
61
62
```java
63
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
64
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
65
import jakarta.validation.*;
66
import java.util.*;
67
68
// Create locator for custom bundle
69
ResourceBundleLocator locator = new PlatformResourceBundleLocator("MyValidationMessages");
70
71
// Use with message interpolator
72
MessageInterpolator interpolator = new ResourceBundleMessageInterpolator(locator);
73
74
ValidatorFactory factory = Validation.byDefaultProvider()
75
.configure()
76
.messageInterpolator(interpolator)
77
.buildValidatorFactory();
78
79
// MyValidationMessages.properties:
80
// user.name.required=User name is required
81
// user.email.invalid=Invalid email format
82
// user.age.range=Age must be between {min} and {max}
83
84
// MyValidationMessages_fr.properties:
85
// user.name.required=Le nom d'utilisateur est requis
86
// user.email.invalid=Format d'email invalide
87
// user.age.range=L'âge doit être entre {min} et {max}
88
```
89
90
### Aggregate Resource Bundle Locator
91
92
Aggregates multiple resource bundle locators into a single combined bundle.
93
94
```java { .api }
95
package org.hibernate.validator.resourceloading;
96
97
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
98
import java.util.List;
99
import java.util.Locale;
100
import java.util.ResourceBundle;
101
102
/**
103
* Aggregates multiple ResourceBundleLocators.
104
* Combines resource bundles from multiple sources into single bundle.
105
* Messages are looked up in order of locators; first match wins.
106
*/
107
class AggregateResourceBundleLocator implements ResourceBundleLocator {
108
/**
109
* Create aggregating locator.
110
*
111
* @param resourceBundleLocators list of locators to aggregate
112
*/
113
AggregateResourceBundleLocator(List<ResourceBundleLocator> resourceBundleLocators);
114
115
/**
116
* Get aggregated resource bundle for specified locale.
117
* Returns AggregateResourceBundle combining all locators.
118
*
119
* @param locale locale for bundle
120
* @return aggregated resource bundle
121
*/
122
@Override
123
ResourceBundle getResourceBundle(Locale locale);
124
}
125
```
126
127
**Usage Example:**
128
129
```java
130
import org.hibernate.validator.resourceloading.*;
131
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
132
import java.util.*;
133
134
// Create multiple resource bundle locators
135
ResourceBundleLocator userMessages =
136
new PlatformResourceBundleLocator("UserMessages");
137
ResourceBundleLocator productMessages =
138
new PlatformResourceBundleLocator("ProductMessages");
139
ResourceBundleLocator orderMessages =
140
new PlatformResourceBundleLocator("OrderMessages");
141
142
// Aggregate them
143
AggregateResourceBundleLocator aggregateLocator =
144
new AggregateResourceBundleLocator(Arrays.asList(
145
userMessages,
146
productMessages,
147
orderMessages
148
));
149
150
// Use aggregated locator
151
MessageInterpolator interpolator = new ResourceBundleMessageInterpolator(aggregateLocator);
152
153
// Message lookup order:
154
// 1. UserMessages.properties
155
// 2. ProductMessages.properties
156
// 3. OrderMessages.properties
157
// First matching key wins
158
```
159
160
### Aggregate Resource Bundle
161
162
Resource bundle that aggregates multiple resource bundles.
163
164
```java { .api }
165
package org.hibernate.validator.resourceloading;
166
167
import java.util.Enumeration;
168
import java.util.Locale;
169
import java.util.ResourceBundle;
170
171
/**
172
* ResourceBundle aggregating multiple resource bundles.
173
* Provides unified view over multiple resource bundle sources.
174
*/
175
class AggregateResourceBundle extends ResourceBundle {
176
/**
177
* Create aggregate bundle.
178
*
179
* @param locale locale for bundle
180
* @param bundlesToAggregate bundles to aggregate
181
*/
182
AggregateResourceBundle(Locale locale, Iterable<ResourceBundle> bundlesToAggregate);
183
184
/**
185
* Get object for key from aggregated bundles.
186
*
187
* @param key message key
188
* @return message value from first bundle containing key
189
*/
190
@Override
191
protected Object handleGetObject(String key);
192
193
/**
194
* Get enumeration of all keys from all aggregated bundles.
195
*
196
* @return enumeration of all keys
197
*/
198
@Override
199
Enumeration<String> getKeys();
200
}
201
```
202
203
### Caching Resource Bundle Locator
204
205
Wraps another resource bundle locator with caching capability.
206
207
```java { .api }
208
package org.hibernate.validator.resourceloading;
209
210
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
211
import java.util.Locale;
212
import java.util.ResourceBundle;
213
214
/**
215
* ResourceBundleLocator with caching support.
216
* Caches resource bundles by locale to avoid repeated loading.
217
*/
218
class CachingResourceBundleLocator implements ResourceBundleLocator {
219
/**
220
* Create caching locator wrapping delegate.
221
*
222
* @param delegate underlying locator to cache
223
*/
224
CachingResourceBundleLocator(ResourceBundleLocator delegate);
225
226
/**
227
* Get cached resource bundle for locale.
228
* Loads from delegate and caches on first request.
229
*
230
* @param locale locale for bundle
231
* @return cached resource bundle
232
*/
233
@Override
234
ResourceBundle getResourceBundle(Locale locale);
235
}
236
```
237
238
**Usage Example:**
239
240
```java
241
import org.hibernate.validator.resourceloading.*;
242
243
// Create base locator
244
ResourceBundleLocator baseLocator =
245
new PlatformResourceBundleLocator("ValidationMessages");
246
247
// Wrap with caching
248
CachingResourceBundleLocator cachingLocator =
249
new CachingResourceBundleLocator(baseLocator);
250
251
// Use caching locator
252
// Subsequent calls with same locale will return cached bundle
253
ResourceBundle bundle1 = cachingLocator.getResourceBundle(Locale.US);
254
ResourceBundle bundle2 = cachingLocator.getResourceBundle(Locale.US);
255
// bundle1 and bundle2 are the same instance (cached)
256
```
257
258
### Delegating Resource Bundle Locator
259
260
Delegates resource bundle loading to another locator.
261
262
```java { .api }
263
package org.hibernate.validator.resourceloading;
264
265
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
266
import java.util.Locale;
267
import java.util.ResourceBundle;
268
269
/**
270
* ResourceBundleLocator that delegates to another locator.
271
* Base class for decorating locators with additional behavior.
272
*/
273
class DelegatingResourceBundleLocator implements ResourceBundleLocator {
274
/**
275
* Create delegating locator.
276
*
277
* @param delegate locator to delegate to
278
*/
279
DelegatingResourceBundleLocator(ResourceBundleLocator delegate);
280
281
/**
282
* Get resource bundle by delegating to wrapped locator.
283
*
284
* @param locale locale for bundle
285
* @return resource bundle from delegate
286
*/
287
@Override
288
ResourceBundle getResourceBundle(Locale locale);
289
}
290
```
291
292
## Complete Resource Bundle Configuration Example
293
294
```java
295
import org.hibernate.validator.resourceloading.*;
296
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
297
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
298
import jakarta.validation.*;
299
import java.util.*;
300
301
/**
302
* Configure complex resource bundle hierarchy:
303
* 1. Application-specific messages
304
* 2. Module-specific messages
305
* 3. Contributor messages
306
* 4. Default ValidationMessages
307
*/
308
class ResourceBundleConfiguration {
309
310
public ValidatorFactory createValidatorFactory() {
311
// Create individual locators
312
ResourceBundleLocator appMessages =
313
new PlatformResourceBundleLocator("ApplicationMessages");
314
315
ResourceBundleLocator userModuleMessages =
316
new PlatformResourceBundleLocator("UserModuleMessages");
317
318
ResourceBundleLocator productModuleMessages =
319
new PlatformResourceBundleLocator("ProductModuleMessages");
320
321
ResourceBundleLocator contributorMessages =
322
new PlatformResourceBundleLocator("ContributorMessages");
323
324
ResourceBundleLocator defaultMessages =
325
new PlatformResourceBundleLocator("ValidationMessages");
326
327
// Aggregate module messages
328
AggregateResourceBundleLocator moduleMessages =
329
new AggregateResourceBundleLocator(Arrays.asList(
330
userModuleMessages,
331
productModuleMessages
332
));
333
334
// Create final hierarchy
335
AggregateResourceBundleLocator allMessages =
336
new AggregateResourceBundleLocator(Arrays.asList(
337
appMessages, // Highest priority
338
moduleMessages,
339
contributorMessages,
340
defaultMessages // Lowest priority
341
));
342
343
// Add caching for performance
344
CachingResourceBundleLocator cachedMessages =
345
new CachingResourceBundleLocator(allMessages);
346
347
// Create message interpolator
348
ResourceBundleMessageInterpolator interpolator =
349
new ResourceBundleMessageInterpolator(cachedMessages);
350
351
// Build validator factory
352
return Validation.byDefaultProvider()
353
.configure()
354
.messageInterpolator(interpolator)
355
.buildValidatorFactory();
356
}
357
}
358
359
// Message lookup order:
360
// 1. ApplicationMessages.properties (app-specific overrides)
361
// 2. UserModuleMessages.properties (user module messages)
362
// 3. ProductModuleMessages.properties (product module messages)
363
// 4. ContributorMessages.properties (contributor messages)
364
// 5. ValidationMessages.properties (defaults)
365
```
366
367
## Custom Resource Bundle Locator
368
369
```java
370
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
371
import java.util.*;
372
373
/**
374
* Custom resource bundle locator loading messages from database.
375
*/
376
class DatabaseResourceBundleLocator implements ResourceBundleLocator {
377
378
private final MessageRepository messageRepository;
379
380
public DatabaseResourceBundleLocator(MessageRepository messageRepository) {
381
this.messageRepository = messageRepository;
382
}
383
384
@Override
385
public ResourceBundle getResourceBundle(Locale locale) {
386
// Load messages from database
387
Map<String, String> messages = messageRepository.findMessagesByLocale(locale);
388
389
// Create resource bundle from database messages
390
return new DatabaseResourceBundle(messages);
391
}
392
}
393
394
/**
395
* ResourceBundle backed by database messages.
396
*/
397
class DatabaseResourceBundle extends ResourceBundle {
398
399
private final Map<String, String> messages;
400
401
public DatabaseResourceBundle(Map<String, String> messages) {
402
this.messages = messages;
403
}
404
405
@Override
406
protected Object handleGetObject(String key) {
407
return messages.get(key);
408
}
409
410
@Override
411
public Enumeration<String> getKeys() {
412
return Collections.enumeration(messages.keySet());
413
}
414
}
415
416
// Usage
417
interface MessageRepository {
418
Map<String, String> findMessagesByLocale(Locale locale);
419
}
420
421
class MessageRepositoryImpl implements MessageRepository {
422
@Override
423
public Map<String, String> findMessagesByLocale(Locale locale) {
424
// Query database for messages
425
Map<String, String> messages = new HashMap<>();
426
// ... load from database ...
427
return messages;
428
}
429
}
430
431
// Configure validator with database messages
432
MessageRepository repository = new MessageRepositoryImpl();
433
ResourceBundleLocator dbLocator = new DatabaseResourceBundleLocator(repository);
434
435
// Combine database messages with file-based messages
436
ResourceBundleLocator fileLocator =
437
new PlatformResourceBundleLocator("ValidationMessages");
438
439
AggregateResourceBundleLocator combinedLocator =
440
new AggregateResourceBundleLocator(Arrays.asList(
441
dbLocator, // Database messages (higher priority)
442
fileLocator // File messages (fallback)
443
));
444
445
MessageInterpolator interpolator =
446
new ResourceBundleMessageInterpolator(combinedLocator);
447
448
ValidatorFactory factory = Validation.byDefaultProvider()
449
.configure()
450
.messageInterpolator(interpolator)
451
.buildValidatorFactory();
452
```
453
454
## Resource Bundle Organization Best Practices
455
456
```java
457
/**
458
* Best practices for organizing validation messages:
459
*
460
* 1. Hierarchical structure:
461
* - ValidationMessages.properties (defaults from Jakarta Validation & Hibernate Validator)
462
* - ApplicationMessages.properties (application-wide overrides)
463
* - ModuleMessages.properties (module-specific messages)
464
* - CustomMessages.properties (custom constraint messages)
465
*
466
* 2. Message key conventions:
467
* - Standard constraints: Use default keys (e.g., jakarta.validation.constraints.NotNull.message)
468
* - Custom constraints: Use fully qualified names (e.g., com.example.constraints.CustomConstraint.message)
469
* - Domain-specific: Use domain prefixes (e.g., user.email.invalid, product.price.negative)
470
*
471
* 3. Localization:
472
* - Create locale-specific files: ValidationMessages_fr.properties, ValidationMessages_de.properties
473
* - Keep keys consistent across all locales
474
* - Provide fallback messages in default bundle
475
*
476
* 4. Parameter naming:
477
* - Use descriptive parameter names: {min}, {max}, {value}, {regexp}
478
* - Document expected parameters in comments
479
*
480
* 5. Performance:
481
* - Use caching locators for frequently accessed bundles
482
* - Preload bundles for known locales at startup
483
* - Avoid complex bundle hierarchies for simple applications
484
*/
485
486
// Example bundle structure:
487
488
// ValidationMessages.properties (defaults)
489
// jakarta.validation.constraints.NotNull.message=must not be null
490
// jakarta.validation.constraints.Size.message=size must be between {min} and {max}
491
492
// ApplicationMessages.properties (overrides)
493
// jakarta.validation.constraints.NotNull.message=This field is required
494
// user.email.invalid=Please provide a valid email address
495
// user.password.weak=Password must contain at least 8 characters with letters and numbers
496
497
// ModuleMessages.properties (module-specific)
498
// order.total.negative=Order total cannot be negative
499
// order.items.empty=Order must contain at least one item
500
// order.discount.invalid=Discount percentage must be between 0 and 100
501
502
// ValidationMessages_fr.properties (French)
503
// jakarta.validation.constraints.NotNull.message=ne doit pas être null
504
// user.email.invalid=Veuillez fournir une adresse email valide
505
506
// Usage
507
ResourceBundleLocator appLocator = new PlatformResourceBundleLocator("ApplicationMessages");
508
ResourceBundleLocator moduleLocator = new PlatformResourceBundleLocator("ModuleMessages");
509
ResourceBundleLocator defaultLocator = new PlatformResourceBundleLocator("ValidationMessages");
510
511
AggregateResourceBundleLocator aggregateLocator = new AggregateResourceBundleLocator(
512
Arrays.asList(appLocator, moduleLocator, defaultLocator)
513
);
514
515
CachingResourceBundleLocator cachingLocator = new CachingResourceBundleLocator(aggregateLocator);
516
517
MessageInterpolator interpolator = new ResourceBundleMessageInterpolator(cachingLocator);
518
```
519