or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdconstraint-validation.mdconstraints.mdindex.mdmessage-interpolation.mdpath-navigation.mdprogrammatic-config.mdresource-loading.mdspi.md

message-interpolation.mddocs/

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