Hibernate Validator is the reference implementation of Jakarta Validation 3.1 providing annotation-based validation for JavaBeans and method parameters
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.
Resource bundle-backed message interpolator with full Expression Language support.
package org.hibernate.validator.messageinterpolation;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import java.util.Locale;
import java.util.Set;
/**
* Resource bundle-backed message interpolator with Expression Language support.
* Default message interpolator for Hibernate Validator.
*/
class ResourceBundleMessageInterpolator extends AbstractMessageInterpolator {
/**
* Create interpolator using default resource bundle ("ValidationMessages").
*/
ResourceBundleMessageInterpolator();
/**
* Create interpolator with custom user resource bundle locator.
*
* @param userResourceBundleLocator locator for user resource bundles
*/
ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator);
/**
* Create interpolator with user and contributor resource bundle locators.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
*/
ResourceBundleMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator);
/**
* Create interpolator with user and contributor locators and caching control.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
* @param cachingEnabled whether to enable message caching
*/
ResourceBundleMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
boolean cachingEnabled);
/**
* Create interpolator with locale preloading support.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
* @param cachingEnabled whether to enable message caching
* @param preloadResourceBundles whether to preload resource bundles for all locales
* @since 6.1.1
*/
@Incubating
ResourceBundleMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
boolean cachingEnabled,
boolean preloadResourceBundles);
/**
* Create interpolator with locale support and preloading.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
* @param cachingEnabled whether to enable message caching
* @param preloadResourceBundles whether to preload resource bundles
* @param defaultLocale default locale for message interpolation
* @param supportedLocales supported locales
* @since 6.1.1
*/
@Incubating
ResourceBundleMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
boolean cachingEnabled,
boolean preloadResourceBundles,
Locale defaultLocale,
Set<Locale> supportedLocales);
}Usage Example:
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import org.hibernate.validator.resourceloading.AggregateResourceBundleLocator;
import jakarta.validation.*;
import java.util.*;
// Create custom resource bundle locators
ResourceBundleLocator userLocator = new PlatformResourceBundleLocator("MyValidationMessages");
ResourceBundleLocator contributorLocator = new PlatformResourceBundleLocator("ContributorMessages");
// Create message interpolator with custom locators
ResourceBundleMessageInterpolator interpolator = new ResourceBundleMessageInterpolator(
userLocator,
contributorLocator,
true, // Enable caching
true, // Preload bundles
Locale.US,
Set.of(Locale.US, Locale.UK, Locale.FRANCE)
);
// Configure validator to use custom interpolator
ValidatorFactory factory = Validation.byDefaultProvider()
.configure()
.messageInterpolator(interpolator)
.buildValidatorFactory();Message interpolator without Expression Language support (parameter values only).
package org.hibernate.validator.messageinterpolation;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import java.util.Locale;
import java.util.Set;
/**
* Resource bundle message interpolator without EL support.
* Only interpolates parameter values, providing better security
* by avoiding expression evaluation. Use when EL features are not needed.
*
* @since 5.2
*/
class ParameterMessageInterpolator extends AbstractMessageInterpolator {
/**
* Create interpolator using default resource bundle.
*/
ParameterMessageInterpolator();
/**
* Create interpolator with custom resource bundle locator.
*
* @param userResourceBundleLocator locator for user resource bundles
*/
ParameterMessageInterpolator(ResourceBundleLocator userResourceBundleLocator);
/**
* Create interpolator with user and contributor locators.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
*/
ParameterMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator);
/**
* Create interpolator with caching control.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
* @param cachingEnabled whether to enable message caching
*/
ParameterMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
boolean cachingEnabled);
/**
* Create interpolator with locale support.
*
* @param userResourceBundleLocator locator for user resource bundles
* @param contributorResourceBundleLocator locator for contributor resource bundles
* @param cachingEnabled whether to enable message caching
* @param preloadResourceBundles whether to preload resource bundles
* @param defaultLocale default locale
* @param supportedLocales supported locales
* @since 6.1.1
*/
@Incubating
ParameterMessageInterpolator(
ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
boolean cachingEnabled,
boolean preloadResourceBundles,
Locale defaultLocale,
Set<Locale> supportedLocales);
}Usage Example:
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
// Use parameter-only interpolator for security
ParameterMessageInterpolator interpolator = new ParameterMessageInterpolator();
ValidatorFactory factory = Validation.byDefaultProvider()
.configure()
.messageInterpolator(interpolator)
.buildValidatorFactory();
// Now only parameter substitution works:
// "{min} <= value <= {max}" -> "0 <= value <= 100"
// EL expressions like "${formatter.format(...)}" will NOT be evaluatedBase class for message interpolators providing common functionality.
package org.hibernate.validator.messageinterpolation;
import jakarta.validation.MessageInterpolator;
import java.util.Locale;
/**
* Base class for message interpolators.
* Provides common functionality and template method for customization.
*/
abstract class AbstractMessageInterpolator implements MessageInterpolator {
/**
* Interpolate message with given context.
*
* @param message message template
* @param context interpolation context
* @return interpolated message
*/
@Override
String interpolate(String message, Context context);
/**
* Interpolate message with given context and locale.
*
* @param message message template
* @param context interpolation context
* @param locale locale for interpolation
* @return interpolated message
*/
@Override
String interpolate(String message, Context context, Locale locale);
}Configure Expression Language features for security control.
package org.hibernate.validator.messageinterpolation;
/**
* Expression Language feature level for controlling which EL features
* are available for message interpolation. Provides security control
* over EL expression evaluation.
*
* @since 6.2
*/
@Incubating
enum ExpressionLanguageFeatureLevel {
/**
* Context-dependent default level.
* For constraints: resolves to BEAN_PROPERTIES (spec-compliant).
* For custom violations: resolves to VARIABLES (safest for user input).
*/
DEFAULT,
/**
* No EL interpolation.
* Only parameter substitution (like {min}, {max}) is performed.
* Safest option, equivalent to ParameterMessageInterpolator.
*/
NONE,
/**
* Only injected variables, formatter, and ResourceBundles.
* EL expressions can access:
* - Variables added via addExpressionVariable()
* - formatter object for formatting
* - ResourceBundle messages
* No access to bean properties or methods.
*/
VARIABLES,
/**
* Variables plus bean properties (specification-compliant minimum).
* EL expressions can access:
* - All VARIABLES features
* - Bean property getters (e.g., ${validatedValue.property})
* No method execution allowed (except property getters).
* This is the Jakarta Validation specification minimum.
*/
BEAN_PROPERTIES,
/**
* Bean properties plus method execution (SECURITY RISK!).
* EL expressions can access:
* - All BEAN_PROPERTIES features
* - Arbitrary method execution (e.g., ${object.method()})
* WARNING: This is a security risk if user input can influence messages.
* Only use in trusted environments.
*/
BEAN_METHODS;
/**
* Parse feature level from string representation.
*
* @param expressionLanguageFeatureLevelString string representation
* @return parsed feature level
* @throws IllegalArgumentException if string is invalid
*/
static ExpressionLanguageFeatureLevel of(String expressionLanguageFeatureLevelString);
/**
* Interpret DEFAULT for constraint messages.
* Returns BEAN_PROPERTIES (spec-compliant) for DEFAULT, otherwise returns input.
*
* @param expressionLanguageFeatureLevel feature level to interpret
* @return interpreted feature level
*/
static ExpressionLanguageFeatureLevel interpretDefaultForConstraints(
ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel);
/**
* Interpret DEFAULT for custom violation messages.
* Returns VARIABLES (safest for user input) for DEFAULT, otherwise returns input.
*
* @param expressionLanguageFeatureLevel feature level to interpret
* @return interpreted feature level
*/
static ExpressionLanguageFeatureLevel interpretDefaultForCustomViolations(
ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel);
}Usage Example:
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
import jakarta.validation.Validation;
// Configure EL feature level for security
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
// Set EL level for static constraint messages
.constraintExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel.BEAN_PROPERTIES)
// Set EL level for custom violations (more restrictive for user input)
.customViolationExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel.VARIABLES)
.buildValidatorFactory();
// Feature level comparison:
// NONE: "{min} <= value <= {max}" -> "0 <= value <= 100"
// VARIABLES: "${myVar} is invalid" -> "custom value is invalid"
// BEAN_PROPERTIES: "${validatedValue.length()} chars" -> "5 chars"
// BEAN_METHODS: "${validatedValue.toUpperCase()}" -> "HELLO" (SECURITY RISK!)Hibernate-specific extension to message interpolator context.
package org.hibernate.validator.messageinterpolation;
import jakarta.validation.MessageInterpolator;
/**
* Hibernate-specific extension to MessageInterpolator.Context.
* Provides additional context information for message interpolation.
*/
interface HibernateMessageInterpolatorContext extends MessageInterpolator.Context {
/**
* Get expression language variables added during validation.
* Variables are added via HibernateConstraintValidatorContext.addExpressionVariable().
*
* @return map of expression language variables
*/
java.util.Map<String, Object> getExpressionVariables();
/**
* Get the root bean being validated.
*
* @return root bean or null if not available
*/
Object getRootBean();
/**
* Get message parameters added during validation.
* Parameters are added via HibernateConstraintValidatorContext.addMessageParameter().
*
* @return map of message parameters
*/
java.util.Map<String, Object> getMessageParameters();
}Usage Example:
import org.hibernate.validator.messageinterpolation.HibernateMessageInterpolatorContext;
import jakarta.validation.MessageInterpolator;
import jakarta.validation.metadata.ConstraintDescriptor;
import java.util.Locale;
import java.util.Map;
// Custom message interpolator using Hibernate context
class CustomMessageInterpolator implements MessageInterpolator {
@Override
public String interpolate(String messageTemplate, Context context) {
return interpolate(messageTemplate, context, Locale.getDefault());
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
// Unwrap to Hibernate context
HibernateMessageInterpolatorContext hibernateContext =
context.unwrap(HibernateMessageInterpolatorContext.class);
// Access additional context information
Map<String, Object> messageParams = hibernateContext.getMessageParameters();
Map<String, Object> expressionVars = hibernateContext.getExpressionVariables();
Object rootBean = hibernateContext.getRootBean();
// Perform custom interpolation
String message = messageTemplate;
// Replace message parameters {paramName}
for (Map.Entry<String, Object> entry : messageParams.entrySet()) {
message = message.replace("{" + entry.getKey() + "}", String.valueOf(entry.getValue()));
}
// Process expression variables if needed
// ...
return message;
}
}// ValidationMessages.properties
javax.validation.constraints.NotNull.message=must not be null
javax.validation.constraints.Size.message=size must be between {min} and {max}
org.hibernate.validator.constraints.Length.message=length must be between {min} and {max}
// Custom messages
user.email.invalid=Email address is invalid
user.age.range=Age must be between {min} and {max}, but was {value}// Using formatter in message templates
@Size(min = 2, max = 10, message = "size is ${validatedValue.length()}, must be between {min} and {max}")
private String name;
// Using validated value
@Pattern(regexp = "[A-Z]+", message = "${validatedValue} must contain only uppercase letters")
private String code;
// Complex expressions with formatter
@DecimalMin(value = "0.0", message = "Value ${formatter.format('%1$.2f', validatedValue)} must be positive")
private BigDecimal amount;import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
import jakarta.validation.*;
class PriceRangeValidator implements ConstraintValidator<PriceRange, BigDecimal> {
private BigDecimal min;
private BigDecimal max;
private String currency;
@Override
public void initialize(PriceRange constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
this.currency = constraintAnnotation.currency();
}
@Override
public boolean isValid(BigDecimal value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (value.compareTo(min) < 0 || value.compareTo(max) > 0) {
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
context.disableDefaultConstraintViolation();
hibernateContext
.addMessageParameter("min", min)
.addMessageParameter("max", max)
.addMessageParameter("currency", currency)
.addMessageParameter("value", value)
.buildConstraintViolationWithTemplate(
"Price {value} {currency} is out of range [{min}, {max}] {currency}")
.addConstraintViolation();
return false;
}
return true;
}
}
// Message output: "Price 150.00 USD is out of range [10.00, 100.00] USD"import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
class StockValidator implements ConstraintValidator<InStock, Integer> {
@Override
public boolean isValid(Integer quantity, ConstraintValidatorContext context) {
if (quantity == null) {
return true;
}
int available = getAvailableStock(); // Get from inventory system
if (quantity > available) {
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
context.disableDefaultConstraintViolation();
hibernateContext
.addExpressionVariable("requested", quantity)
.addExpressionVariable("available", available)
.addExpressionVariable("shortage", quantity - available)
.buildConstraintViolationWithTemplate(
"Requested ${requested} items, only ${available} available (shortage: ${shortage})")
.addConstraintViolation();
return false;
}
return true;
}
private int getAvailableStock() {
return 50; // Example
}
}
// Message output: "Requested 75 items, only 50 available (shortage: 25)"// ValidationMessages.properties (default English)
user.age.invalid=Age must be between {min} and {max}
// ValidationMessages_fr.properties (French)
user.age.invalid=L'âge doit être entre {min} et {max}
// ValidationMessages_de.properties (German)
user.age.invalid=Das Alter muss zwischen {min} und {max} liegen
// ValidationMessages_es.properties (Spanish)
user.age.invalid=La edad debe estar entre {min} y {max}
// Use with locale
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);
for (ConstraintViolation<User> violation : violations) {
// Get message in different locales
MessageInterpolator interpolator = factory.getMessageInterpolator();
String englishMessage = interpolator.interpolate(
violation.getMessageTemplate(),
() -> violation.getConstraintDescriptor(),
Locale.ENGLISH
);
String frenchMessage = interpolator.interpolate(
violation.getMessageTemplate(),
() -> violation.getConstraintDescriptor(),
Locale.FRENCH
);
System.out.println("EN: " + englishMessage);
System.out.println("FR: " + frenchMessage);
}Install with Tessl CLI
npx tessl i tessl/maven-org-hibernate-validator--hibernate-validator