Java Bean validation specifications require that each constraint annotation must include a 'message' element definition.
The message element value represents validation error message.
This element usage is optional on client side. If not specified a default message resource key is resolved and displayed.
Let's look at @NotNull annotation definition:
package javax.validation.constraints;
.....
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
...
}
The values of default resource keys (javax.validation.constraints.NotNull.message in above example) are provided by hibernate (the Bean validation implementation).
Followings are the important things to know regarding messages:
- When creating new constraint annotation, the message element must be included. We can specify a default resource key for the message element or we can put a hard-coded message text. Specifying a resource key is recommended
In case of resource key, it must be within curly brackets {..}
- Per conventions, the resource key should be fully qualified annotation class name appended with '.message', as seen in above
@NotNull definition. That means messages should be the same through out the application for a given annotation and should not tie to a particular use on the client side.
- Client can override the default messages on a particular annotation usage.
@NotNull(message="name must not be null to be valid")
String name; In above snippet, client can also use a new resource key instead. In that case we have to use curly brackets: @NotNull(message="{myBean.name.null.message}")
String name;
- If we want to override default messages with our resource keys or even the default keys, then the resource files must be in classpath and should be named as 'ValidationMessages_fr.properties' i.e. they should always start with 'ValidationMessages' then with underscore separated language code (locale). This is the default behavior which can be modified by implementing javax.validation.MessageInterpolator
Use of Expression language
Java Bean Validation specification supports Unified Expression Language (JSR 341) for evaluating dynamic expression in the validation messages that's the reason we have to include 'javax.el' dependency in our projects.
The value substitutions
Within the messages, constraint annotation element values can be referenced as '{theElementName}'.
For example consider @Min(5) or @Min(value=5) Here we can use messages embedded with {value}, for example 'The min value must be {value}'. Here {value} will be replace with 5 in case of validation error.
The expression can be evaluated for inline messages or in resource bundle files as well
Here are few examples from the Hibernate message resource implementation, hibernate-validator-5.2.4.Final.jar!\org\hibernate\validator\ValidationMessages.properties:
javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Max.message = must be less than or equal to {value}
javax.validation.constraints.Min.message = must be greater than or equal to {value}
javax.validation.constraints.Pattern.message = must match "{regexp}"
javax.validation.constraints.Size.message = size must be between {min} and {max}
Using ${validatedValue}
This is the the currently validated value (property, bean, method parameter etc).
For example:
public class TestBean {
@Size(min = 5, message ="The name '${validatedValue}' must be at least {min}" +
" characters long. Length found : ${validatedValue.length()}")
private String name;
// getters and setters
}
Now if we populate our bean like this and run the validations:
public static void main (String[] args) {
TestBean testBean = new TestBean();
testBean.setName("Mike");
Validator validator = getValidator();
validator.validate(testBean).stream().forEach(ValidatedValueExample::printError);
}
Output:
The name 'Mike' must be at least 5 characters long. Length found : 4
EL restrictions
Starting Hibernate Validator 6.2, a security risk was addressed. If user input is used for the bean values (the bean to be validated), arbitrary strings can be passed to the EL. These arbitrary strings might contain carefully crafted content to either expose sensitive data (a password hash present in a User bean for instance) or execute code. This is very similar to SQL injection vulnerability.
Starting 6.2 Hibernate introduced following level of EL restriction (via ExpressionLanguageFeatureLevel enum)
NONE Expression Language interpolation is fully disabled.
VARIABLES Allow interpolation of the variables injection only
BEAN_PROPERTIES Allow everything VARIABLES allows plus the interpolation of bean properties. (default)
BEAN_METHODS Allow everything BEAN_PROPERTIES allows plus the interpolation from execution of bean methods.
Changing the default level.
ValidatorFactory validatorFactory =
Validation.byProvider( HibernateValidator.class )
.configure()
.customViolationExpressionLanguageFeatureLevel(
ExpressionLanguageFeatureLevel.BEAN_METHODS)
.buildValidatorFactory();
OR
Configuration<?> config = Validation.byDefaultProvider().configure();
((ConfigurationImpl)config).constraintExpressionLanguageFeatureLevel(
ExpressionLanguageFeatureLevel.BEAN_METHODS);
ValidatorFactory factory = config.buildValidatorFactory();
Complete Examples
src/main/resources/ValidationMessages_en.propertiesmyBean.myString.null.msg = myString cannot be null
myBean.myString.pattern.msg = myString shouldn't contain numbers. regex={regexp}, value found=${validatedValue}
javax.validation.constraints.Future.message = Date must be in future. found: ${validatedValue}
Overriding default key
In this example we are going to override default resource key value of Future .
The Future definition snippet:
public @interface Future {
String message() default "{javax.validation.constraints.Future.message}";
...
}
In above ValidationMessages_en.properties, we have already overridden the resource key as:
javax.validation.constraints.Future.message=Date must be in future. found: ${validatedValue}
Example code:
package com.logicbig.example;
import javax.validation.*;
import javax.validation.constraints.Future;
import java.util.Date;
import java.util.Locale;
public class OverrideDefaultKeyExample {
private static class MyBean {
@Future
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
public static void main(String[] args) {
MyBean myBean = new MyBean();
myBean.setDate(new Date(System.currentTimeMillis() - 100000));
Locale.setDefault(Locale.US);//Locale.FRANCE
Configuration<?> config = Validation.byDefaultProvider().configure();
ValidatorFactory factory = config.buildValidatorFactory();
Validator validator = factory.getValidator();
validator.validate(myBean).stream()
.forEach(OverrideDefaultKeyExample::printError);
factory.close();
}
private static void printError(
ConstraintViolation<MyBean> violation) {
System.out.println(violation.getMessage());
}
}
OutputDate must be in future. found: Wed Jul 28 03:00:29 CDT 2021
Providing different key
package com.logicbig.example;
import javax.validation.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.util.Locale;
public class ProvidingDifferentKeyExample {
private static class MyBean {
private String myString;
@NotNull(message = "{myBean.myString.null.msg}")
@Pattern(regexp = "\\D+", message = "{myBean.myString.pattern.msg}")
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
}
public static void main(String[] args) {
MyBean myBean = new MyBean();
//myBean.setMyString("jackie10");
Locale.setDefault(Locale.US);//Locale.FRANCE
Configuration<?> config = Validation.byDefaultProvider().configure();
ValidatorFactory factory = config.buildValidatorFactory();
Validator validator = factory.getValidator();
validator.validate(myBean).stream().forEach(ProvidingDifferentKeyExample::printError);
factory.close();
}
private static void printError(
ConstraintViolation<MyBean> violation) {
System.out.println(violation.getMessage());
}
}
OutputmyString cannot be null
Using Using ${validatedValue}
package com.logicbig.example;
import org.hibernate.validator.internal.engine.ConfigurationImpl;
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
import javax.validation.*;
import javax.validation.constraints.Size;
public class ValidatedValueExample {
private static class TestBean {
@Size(min = 5, message = "The name '${validatedValue}' must be at least {min}" +
" characters long. Length found: '${validatedValue.length()}'")
private String name;
public String getName () {
return name;
}
public void setName (String name) {
this.name = name;
}
}
public static void main (String[] args) {
TestBean testBean = new TestBean();
testBean.setName("Mike");
Configuration<?> config = Validation.byDefaultProvider().configure();
//6.2.0.Final
((ConfigurationImpl)config).constraintExpressionLanguageFeatureLevel(
ExpressionLanguageFeatureLevel.BEAN_METHODS);
ValidatorFactory factory = config.buildValidatorFactory();
Validator validator = factory.getValidator();
validator.validate(testBean).stream().forEach(ValidatedValueExample::printError);
factory.close();
}
private static void printError (
ConstraintViolation<TestBean> violation) {
System.out.println(violation.getMessage());
}
}
OutputThe name 'Mike' must be at least 5 characters long. Length found: '4'
Example Project
Dependencies and Technologies Used: - hibernate-validator 6.2.0.Final (Hibernate's Jakarta Bean Validation reference implementation)
Version Compatibility: 5.0.0.Final - 6.2.0.Final Version compatibilities of hibernate-validator with this example: groupId: org.hibernate artifactId: hibernate-validator Reference implementation for Bean Validation 1.1
- 5.0.0.Final
- 5.0.1.Final
- 5.0.2.Final
- 5.0.3.Final
- 5.1.0.Final
- 5.1.1.Final
- 5.1.2.Final
- 5.1.3.Final
- 5.2.0.Final
- 5.2.1.Final
- 5.2.2.Final
- 5.2.3.Final
- 5.2.4.Final
- 5.2.5.Final
- 5.3.0.Final
- 5.3.1.Final
- 5.3.2.Final
- 5.3.3.Final
- 5.3.4.Final
- 5.3.5.Final
- 5.3.6.Final
- 5.4.0.Final
- 5.4.1.Final
- 5.4.2.Final
- 5.4.3.Final
groupId: org.hibernate.validator artifactId: hibernate-validator Reference implementation for Bean Validation 2.0
- 6.0.0.Final
- 6.0.1.Final
- 6.0.2.Final
- 6.0.3.Final
- 6.0.4.Final
- 6.0.5.Final
- 6.0.6.Final
- 6.0.7.Final
- 6.0.8.Final
- 6.0.9.Final
- 6.0.10.Final
- 6.0.11.Final
- 6.0.12.Final
- 6.0.13.Final
- 6.0.14.Final
- 6.0.15.Final
- 6.0.16.Final
- 6.0.17.Final
- 6.0.18.Final
- 6.0.19.Final
- 6.0.20.Final
- 6.0.21.Final
- 6.0.22.Final
- 6.1.0.Final
- 6.1.1.Final
- 6.1.2.Final
- 6.1.3.Final
- 6.1.4.Final
- 6.1.5.Final
- 6.1.6.Final
- 6.1.7.Final
- 6.2.0.Final
Version 7 and later: Jakarta Bean Validation 3.0 jakarta.* packages
Versions in green have been tested.
- javax.el 3.0.0 (Expression Language 3.0)
- JDK 1.8
- Maven 3.8.1
|