Hibernate Validator is the reference implementation of Jakarta Validation 3.1 providing annotation-based validation for JavaBeans and method parameters
Hibernate Validator extensions to the constraint validator API providing enhanced initialization context, dynamic message parameters, expression language variables, dynamic payloads, and constraint validator payload access for advanced custom validation logic.
Enhanced constraint validator interface with Hibernate-specific initialization.
package org.hibernate.validator.constraintvalidation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.metadata.ConstraintDescriptor;
import java.lang.annotation.Annotation;
/**
* Hibernate Validator extension to ConstraintValidator.
* Provides enhanced initialization with additional Hibernate-specific context.
*
* @param <A> constraint annotation type
* @param <T> target type this validator can validate
* @since 6.0.5
*/
@Incubating
interface HibernateConstraintValidator<A extends Annotation, T> extends ConstraintValidator<A, T> {
/**
* Initialize validator with enhanced Hibernate-specific context.
* This method is called instead of initialize(A) when using Hibernate Validator.
*
* @param constraintDescriptor descriptor for the constraint
* @param initializationContext Hibernate-specific initialization context
*/
default void initialize(
ConstraintDescriptor<A> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext
) {
// Default implementation delegates to standard initialize method
initialize(constraintDescriptor.getAnnotation());
}
}Usage Example:
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidator;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;
import jakarta.validation.metadata.ConstraintDescriptor;
public class MyValidator implements HibernateConstraintValidator<MyConstraint, String> {
private String pattern;
@Override
public void initialize(
ConstraintDescriptor<MyConstraint> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext
) {
MyConstraint annotation = constraintDescriptor.getAnnotation();
this.pattern = annotation.pattern();
// Access Hibernate-specific initialization context
// (future extensions may add more context information here)
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return value.matches(pattern);
}
}Provides contextual data and operations when initializing a constraint validator, including access to script evaluators, clock providers, and temporal validation tolerance.
package org.hibernate.validator.constraintvalidation;
import jakarta.validation.ClockProvider;
import java.time.Duration;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
/**
* Provides contextual data and operations when initializing a constraint validator.
*
* @since 6.0.5
*/
@Incubating
interface HibernateConstraintValidatorInitializationContext {
/**
* Returns a ScriptEvaluator for the given scripting language.
* The ScriptEvaluator is created by the ScriptEvaluatorFactory passed at bootstrap.
*
* @param languageName the name of the scripting language (e.g., "javascript", "groovy")
* @return script evaluator for the given language, never null
* @throws ScriptEvaluatorNotFoundException if no evaluator found for the language
*/
ScriptEvaluator getScriptEvaluatorForLanguage(String languageName);
/**
* Returns the provider for obtaining the current time as a Clock.
* Used for validating temporal constraints like @Future and @Past.
*
* @return the clock provider, never null. If not configured, returns default
* implementation using current system time and default time zone.
*/
ClockProvider getClockProvider();
/**
* Returns the temporal validation tolerance (acceptable margin of error
* when comparing date/time in temporal constraints).
*
* @return the tolerance duration
* @since 6.0.5
*/
@Incubating
Duration getTemporalValidationTolerance();
}Usage Example:
import org.hibernate.validator.constraintvalidation.*;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import jakarta.validation.ClockProvider;
import jakarta.validation.metadata.ConstraintDescriptor;
import java.time.*;
public class AdvancedValidator implements HibernateConstraintValidator<AdvancedConstraint, String> {
private ScriptEvaluator scriptEvaluator;
private Clock clock;
private Duration tolerance;
@Override
public void initialize(
ConstraintDescriptor<AdvancedConstraint> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext
) {
// Access script evaluator for dynamic validation logic
this.scriptEvaluator = initializationContext
.getScriptEvaluatorForLanguage("javascript");
// Access clock for temporal validation
ClockProvider clockProvider = initializationContext.getClockProvider();
this.clock = clockProvider.getClock();
// Access temporal tolerance for fuzzy time comparisons
this.tolerance = initializationContext.getTemporalValidationTolerance();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// Use initialized resources for validation
LocalDateTime now = LocalDateTime.now(clock);
// ... validation logic using script evaluator, clock, and tolerance
return true;
}
}Enhanced validation context with dynamic message parameters, expression language variables, and dynamic payloads.
package org.hibernate.validator.constraintvalidation;
import jakarta.validation.ConstraintValidatorContext;
/**
* Hibernate-specific extension to ConstraintValidatorContext.
* Provides additional capabilities for message parameters, expression variables,
* dynamic payloads, and constraint validator payload access.
*/
interface HibernateConstraintValidatorContext extends ConstraintValidatorContext {
/**
* Add named message parameter for interpolation.
* Parameters can be referenced in message templates as {paramName}.
*
* @param name parameter name
* @param value parameter value (will be converted to string)
* @return this context for method chaining
* @since 5.4.1
*/
HibernateConstraintValidatorContext addMessageParameter(String name, Object value);
/**
* Add expression language variable for message interpolation.
* Variables can be used in EL expressions within message templates.
*
* @param name variable name
* @param value variable value
* @return this context for method chaining
*/
HibernateConstraintValidatorContext addExpressionVariable(String name, Object value);
/**
* Set dynamic payload for constraint violation.
* Payload can be retrieved from HibernateConstraintViolation.
*
* @param payload payload object
* @return this context for method chaining
* @since 5.3
*/
HibernateConstraintValidatorContext withDynamicPayload(Object payload);
/**
* Get constraint validator payload passed during configuration.
* Payload is set via HibernateValidatorContext.constraintValidatorPayload().
*
* @param <C> payload type
* @param type payload class
* @return constraint validator payload or null if not set
* @since 6.0.9
*/
@Incubating
<C> C getConstraintValidatorPayload(Class<C> type);
/**
* Build constraint violation with Hibernate-specific features.
* Returns HibernateConstraintViolationBuilder for additional configuration.
*
* @param messageTemplate message template
* @return Hibernate constraint violation builder
*/
@Override
HibernateConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);
}Usage Example:
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class RangeValidator implements ConstraintValidator<Range, Integer> {
private int min;
private int max;
@Override
public void initialize(Range constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (value < min || value > max) {
// Unwrap to Hibernate-specific context
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
// Disable default violation
context.disableDefaultConstraintViolation();
// Add message parameters
hibernateContext
.addMessageParameter("min", min)
.addMessageParameter("max", max)
.addMessageParameter("value", value)
// Add EL variable
.addExpressionVariable("outOfRange", Math.abs(value - (min + max) / 2))
// Set dynamic payload with additional info
.withDynamicPayload(new RangeValidationInfo(value, min, max))
// Build custom message
.buildConstraintViolationWithTemplate(
"Value {value} is out of range [{min}, {max}]")
.addConstraintViolation();
return false;
}
return true;
}
}
// Payload class for additional validation info
class RangeValidationInfo {
private final int value;
private final int min;
private final int max;
RangeValidationInfo(int value, int min, int max) {
this.value = value;
this.min = min;
this.max = max;
}
// Getters...
}Enhanced constraint violation builder with Hibernate-specific features.
package org.hibernate.validator.constraintvalidation;
import jakarta.validation.ConstraintValidatorContext;
/**
* Hibernate-specific extension to ConstraintViolationBuilder.
* Provides enhanced constraint violation building capabilities.
*/
interface HibernateConstraintViolationBuilder
extends ConstraintValidatorContext.ConstraintViolationBuilder {
/**
* Add node to constraint violation property path.
*
* @param name node name
* @return node builder context
*/
@Override
NodeBuilderDefinedContext addNode(String name);
/**
* Add property node to constraint violation property path.
*
* @param name property name
* @return node builder defined context
*/
@Override
NodeBuilderCustomizableContext addPropertyNode(String name);
/**
* Add bean node to constraint violation property path.
*
* @return node builder defined context
*/
@Override
NodeBuilderCustomizableContext addBeanNode();
/**
* Add parameter node to constraint violation property path.
*
* @param index parameter index
* @return node builder defined context
*/
@Override
LeafNodeBuilderCustomizableContext addParameterNode(int index);
/**
* Add container element node to constraint violation property path.
*
* @param name property name
* @param containerType container type
* @param typeArgumentIndex type argument index
* @return node builder defined context
*/
@Override
ContainerElementNodeBuilderDefinedContext addContainerElementNode(
String name, Class<?> containerType, Integer typeArgumentIndex);
}Enhanced constraint violation containing dynamic payload.
package org.hibernate.validator.engine;
import jakarta.validation.ConstraintViolation;
/**
* Custom ConstraintViolation with additional Hibernate-specific information.
* Provides access to dynamic payload set during validation.
*
* @param <T> root bean type
* @since 5.3
*/
interface HibernateConstraintViolation<T> extends ConstraintViolation<T> {
/**
* Get dynamic payload set via HibernateConstraintValidatorContext.
* Returns null if no payload was set.
*
* @param <C> payload type
* @param type payload class
* @return dynamic payload or null
*/
<C> C getDynamicPayload(Class<C> type);
}Usage Example:
import org.hibernate.validator.engine.HibernateConstraintViolation;
import jakarta.validation.ConstraintViolation;
import java.util.Set;
// Validate object
Set<ConstraintViolation<User>> violations = validator.validate(user);
// Process violations and extract dynamic payloads
for (ConstraintViolation<User> violation : violations) {
// Unwrap to Hibernate-specific violation
HibernateConstraintViolation<User> hibernateViolation =
violation.unwrap(HibernateConstraintViolation.class);
// Get dynamic payload if available
RangeValidationInfo info = hibernateViolation.getDynamicPayload(RangeValidationInfo.class);
if (info != null) {
System.out.println("Range violation details:");
System.out.println(" Value: " + info.getValue());
System.out.println(" Min: " + info.getMin());
System.out.println(" Max: " + info.getMax());
}
System.out.println("Message: " + violation.getMessage());
System.out.println("Property: " + violation.getPropertyPath());
}Hibernate-specific context for cross-parameter constraint validators, extending HibernateConstraintValidatorContext with access to method parameter names.
package org.hibernate.validator.constraintvalidation;
import java.util.List;
/**
* Hibernate-specific context for cross-parameter constraint validators.
* Extends HibernateConstraintValidatorContext with additional functionality
* for accessing method parameter names during cross-parameter validation.
*
* @since 6.1.0
*/
@Incubating
interface HibernateCrossParameterConstraintValidatorContext extends HibernateConstraintValidatorContext {
/**
* Returns the list of parameter names of the validated method.
* Useful for providing detailed error messages that reference specific parameters by name.
*
* @return list of method parameter names, never null
*/
List<String> getMethodParameterNames();
}Note: This interface extends HibernateConstraintValidatorContext, so all methods from that interface are also available:
addMessageParameter(String name, Object value)addExpressionVariable(String name, Object value)withDynamicPayload(Object payload)getConstraintValidatorPayload(Class<C> type)buildConstraintViolationWithTemplate(String messageTemplate)Usage Example:
import org.hibernate.validator.constraintvalidation.HibernateCrossParameterConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.List;
public class CrossParamValidator
implements ConstraintValidator<CrossParamConstraint, Object[]> {
@Override
public boolean isValid(Object[] parameters, ConstraintValidatorContext context) {
if (parameters[0] == null || parameters[1] == null) {
return true;
}
// Unwrap to Hibernate cross-parameter context
HibernateCrossParameterConstraintValidatorContext hibernateContext =
context.unwrap(HibernateCrossParameterConstraintValidatorContext.class);
// Get parameter names for detailed error messages
List<String> paramNames = hibernateContext.getMethodParameterNames();
if (isInvalid(parameters)) {
context.disableDefaultConstraintViolation();
hibernateContext
.addMessageParameter("param1Name", paramNames.get(0))
.addMessageParameter("param2Name", paramNames.get(1))
.addMessageParameter("param1Value", parameters[0])
.addMessageParameter("param2Value", parameters[1])
.buildConstraintViolationWithTemplate(
"Parameters {param1Name}={param1Value} and {param2Name}={param2Value} are incompatible")
.addConstraintViolation();
return false;
}
return true;
}
}Default implementation of constraint validator factory.
package org.hibernate.validator.constraintvalidation.spi;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorFactory;
/**
* Default implementation of ConstraintValidatorFactory.
* Creates validator instances using zero-argument constructors.
*/
class DefaultConstraintValidatorFactory implements ConstraintValidatorFactory {
/**
* Create instance of constraint validator.
* Uses zero-argument constructor.
*
* @param <T> validator type
* @param key validator class
* @return validator instance
*/
@Override
<T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key);
/**
* Release validator instance.
* Default implementation does nothing.
*
* @param instance validator instance to release
*/
@Override
void releaseInstance(ConstraintValidator<?, ?> instance);
}Alternative URL validator using regular expressions instead of java.net.URL.
package org.hibernate.validator.constraintvalidators;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.hibernate.validator.constraints.URL;
/**
* Regular expression-based URL validator.
* Can be used as alternative to default java.net.URL-based validator
* for validating non-standard protocols or URL formats.
*/
class RegexpURLValidator implements ConstraintValidator<URL, CharSequence> {
/**
* Initialize validator with constraint annotation.
*
* @param url URL constraint annotation
*/
@Override
void initialize(URL url);
/**
* Validate URL using regular expression matching.
*
* @param value URL string to validate
* @param context validation context
* @return true if valid, false otherwise
*/
@Override
boolean isValid(CharSequence value, ConstraintValidatorContext context);
}Usage Example:
// Configure to use RegexpURLValidator via XML or programmatic API
// In META-INF/validation.xml or constraint mapping:
ConstraintMapping mapping = configuration.createConstraintMapping();
mapping.constraintDefinition(URL.class)
.includeExistingValidators(false)
.validatedBy(CharSequence.class, RegexpURLValidator.class);import jakarta.validation.*;
import jakarta.validation.constraints.Pattern;
import org.hibernate.validator.constraintvalidation.*;
import org.hibernate.validator.engine.HibernateConstraintViolation;
import java.lang.annotation.*;
// 1. Define custom constraint annotation
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
@interface PhoneNumber {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String countryCode() default "US";
}
// 2. Create validator using Hibernate extensions
class PhoneNumberValidator implements HibernateConstraintValidator<PhoneNumber, String> {
private String countryCode;
private PhoneValidationRules rules;
@Override
public void initialize(
ConstraintDescriptor<PhoneNumber> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext
) {
PhoneNumber annotation = constraintDescriptor.getAnnotation();
this.countryCode = annotation.countryCode();
this.rules = PhoneValidationRules.forCountry(countryCode);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return true;
}
// Unwrap to Hibernate context for enhanced features
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
// Normalize phone number
String normalized = normalizePhoneNumber(value);
// Validate format
if (!rules.matches(normalized)) {
context.disableDefaultConstraintViolation();
hibernateContext
.addMessageParameter("countryCode", countryCode)
.addMessageParameter("format", rules.getFormat())
.addMessageParameter("example", rules.getExample())
.addExpressionVariable("providedValue", value)
.withDynamicPayload(new PhoneValidationDetails(
value, normalized, countryCode, rules.getFormat()))
.buildConstraintViolationWithTemplate(
"Invalid phone number for {countryCode}. Expected format: {format}")
.addConstraintViolation();
return false;
}
return true;
}
private String normalizePhoneNumber(String phone) {
return phone.replaceAll("[^0-9+]", "");
}
}
// 3. Dynamic payload class
class PhoneValidationDetails {
private final String originalValue;
private final String normalizedValue;
private final String countryCode;
private final String expectedFormat;
PhoneValidationDetails(String originalValue, String normalizedValue,
String countryCode, String expectedFormat) {
this.originalValue = originalValue;
this.normalizedValue = normalizedValue;
this.countryCode = countryCode;
this.expectedFormat = expectedFormat;
}
// Getters...
public String getOriginalValue() { return originalValue; }
public String getNormalizedValue() { return normalizedValue; }
public String getCountryCode() { return countryCode; }
public String getExpectedFormat() { return expectedFormat; }
}
// 4. Use the constraint
class Contact {
@PhoneNumber(countryCode = "US")
private String phoneNumber;
// Getters and setters...
}
// 5. Validate and extract payload
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Contact contact = new Contact();
contact.setPhoneNumber("invalid");
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
for (ConstraintViolation<Contact> violation : violations) {
HibernateConstraintViolation<Contact> hv =
violation.unwrap(HibernateConstraintViolation.class);
PhoneValidationDetails details = hv.getDynamicPayload(PhoneValidationDetails.class);
if (details != null) {
System.out.println("Phone validation failed:");
System.out.println(" Original: " + details.getOriginalValue());
System.out.println(" Normalized: " + details.getNormalizedValue());
System.out.println(" Expected format: " + details.getExpectedFormat());
}
}Pass configuration data to validators at runtime.
import org.hibernate.validator.HibernateValidatorContext;
import org.hibernate.validator.HibernateValidatorFactory;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
import jakarta.validation.*;
// 1. Create payload with validation configuration
class ValidationConfig {
private boolean strictMode;
private String apiKey;
public ValidationConfig(boolean strictMode, String apiKey) {
this.strictMode = strictMode;
this.apiKey = apiKey;
}
// Getters...
public boolean isStrictMode() { return strictMode; }
public String getApiKey() { return apiKey; }
}
// 2. Create validator that uses payload
class StrictEmailValidator implements ConstraintValidator<Email, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
// Get validator payload
ValidationConfig config = hibernateContext.getConstraintValidatorPayload(ValidationConfig.class);
if (config != null && config.isStrictMode()) {
// Use strict validation rules
return validateStrictEmail(value, config.getApiKey());
} else {
// Use lenient validation rules
return validateBasicEmail(value);
}
}
private boolean validateStrictEmail(String email, String apiKey) {
// Perform strict validation (e.g., verify domain exists via API)
return true; // Implementation details omitted
}
private boolean validateBasicEmail(String email) {
// Basic format validation
return email.contains("@");
}
}
// 3. Configure validator with payload
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
HibernateValidatorFactory hibernateFactory = factory.unwrap(HibernateValidatorFactory.class);
ValidationConfig config = new ValidationConfig(true, "api-key-123");
Validator validator = hibernateFactory.usingContext()
.constraintValidatorPayload(config)
.getValidator();
// 4. Use validator (payload is available to all validators)
User user = new User();
user.setEmail("test@example.com");
Set<ConstraintViolation<User>> violations = validator.validate(user);Marker interface for beans that provide optimized property value access, avoiding reflection overhead during validation.
package org.hibernate.validator.engine;
/**
* Hibernate Validator specific marker interface for performance optimization.
* Beans implementing this interface provide direct property value access methods
* instead of using reflection.
*
* Important: When implementing this interface, provide access to ALL constrained
* getters and fields for the class and all its superclasses. Otherwise,
* IllegalArgumentException may be thrown by the validation engine.
*
* @since 6.1
*/
@Incubating
interface HibernateValidatorEnhancedBean {
/** Method name for field value access */
String GET_FIELD_VALUE_METHOD_NAME = "$$_hibernateValidator_getFieldValue";
/** Method name for getter value access */
String GET_GETTER_VALUE_METHOD_NAME = "$$_hibernateValidator_getGetterValue";
/**
* Get the value of a field property by name.
*
* @param name the field name
* @return the field value
* @throws IllegalArgumentException if no field exists with the given name
*/
Object $$_hibernateValidator_getFieldValue(String name);
/**
* Get the value returned by a getter property by name.
*
* @param name the getter name
* @return the getter return value
* @throws IllegalArgumentException if no getter exists with the given name
*/
Object $$_hibernateValidator_getGetterValue(String name);
}Usage Example:
import org.hibernate.validator.engine.HibernateValidatorEnhancedBean;
import jakarta.validation.constraints.*;
/**
* Enhanced bean with optimized property access.
* This is typically generated by bytecode enhancement tools, not manually written.
*/
public class User implements HibernateValidatorEnhancedBean {
@NotNull
@Size(min = 3, max = 50)
private String username;
@NotNull
@Email
private String email;
private int age;
@Min(18)
public int getAge() {
return age;
}
// Optimized field access - avoids reflection
@Override
public Object $$_hibernateValidator_getFieldValue(String name) {
switch (name) {
case "username":
return username;
case "email":
return email;
case "age":
return age;
default:
throw new IllegalArgumentException("Unknown field: " + name);
}
}
// Optimized getter access - avoids reflection
@Override
public Object $$_hibernateValidator_getGetterValue(String name) {
switch (name) {
case "age":
return getAge();
default:
throw new IllegalArgumentException("Unknown getter: " + name);
}
}
// Standard getters and setters...
}
// Usage: Enhanced beans validate faster than regular beans
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
User user = new User();
user.setUsername("jo"); // Too short
user.setEmail("invalid"); // Invalid email
// Validation uses optimized property access methods (no reflection)
Set<ConstraintViolation<User>> violations = validator.validate(user);
// Note: In practice, enhanced beans are generated by build-time tools
// or bytecode enhancement plugins, not manually implementedInstall with Tessl CLI
npx tessl i tessl/maven-org-hibernate-validator--hibernate-validator