I am using bean validation specification to validate my form on spring-boot thymeleaf project.
My entity property is as follow.
#NotEmpty(message = "{Password should not be empty}")
#Pattern(regexp = //Pattern for range 1-20, message = "{Wrong Input}")
private String password;
When I run and inputed to password field of my form with empty value, both of Validation Error Messages were shown.
My expectation is, while empty value is inputed, only #NotEmpty annotation should work and on the other hand, only #Pattern should be shown upon user input is wrong.
How can I do with Bean Validation Specification for that?
Regards.
1. Validation groups
#NotEmpty(groups = First.class), message = ...,
#Pattern(groups = Second.class, regexp = ...)
private String password;
Create the validation groups:
//Validation Groups - Just empty interface used as Group identifier
public interface First {
}
public interface Second {
}
and validate the model this way:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Model>> violations = validator.validate(model, First.class);
if(violations.isEmpty()){
violations = validator.validate(model, Second.class);
}
2. Groups Sequences
I've never used them, but it seems it does just what you want
Check this other answer (https://stackoverflow.com/a/7779681/641627). I've added below a quote from this answer (from #Gunnar), which ironically also uses First and Second group names:
#GroupSequence({First.class, Second.class})
public interface Sequence {}
#Size(min = 2, max = 10, message = "Name length improper", groups = { First.class })
#Pattern(regexp = "T.*", message = "Name doesn't start with T" , groups = { Second.class })
private String name;
When now validating a Bean instance using the defined sequence
(validator.validate(bean, Sequence.class)), at first the #Size
constraint will be validated and only if that succeeds the #Pattern
constraint.
With this solution, you wouldn't need to manually call validator.validate(...), the validations would be performed in the order defined in the Sequence with short-circuit if one fails.
Related
Given a username field which cannot be null, must be between 4 and 64 characters long and match the regexp [A-Za-z0-9]+, when the username field is null, the error message is simply: must not be null. The desired outcome is must not be null AND length must be between 4 and 64 characters AND must match "[A-Za-z0-9]+".
Initial setup:
#NotNull
#Length(min = 4, max = 64)
#Pattern(regexp = "[A-Za-z0-9]+")
String username;
What I also tried:
#NotNull(message = """
{jakarta.validation.constraints.NotNull.message}
AND {org.hibernate.validator.constraints.Length.message}
AND {jakarta.validation.constraints.Pattern.message}""")
#Length(min = 4, max = 64)
#Pattern(regexp = "[A-Za-z0-9]+")
String username;
But this latter's outcome is:
ConstraintViolationImpl{
interpolatedMessage='must not be null AND length must be between {min} and {max} AND must match "{regexp}"',
propertyPath=username, rootBeanClass=class app.User,
messageTemplate='{jakarta.validation.constraints.NotNull.message} AND {org.hibernate.validator.constraints.Length.message} AND {jakarta.validation.constraints.Pattern.message}'}
The values for the constraints (min, max, regexp) are not accessed. How to render the actual values of these in the error message?
Most constraint annotations, including #Length and #Pattern, regard null as valid input. That's why you won't get what you want by just using these annotations.
Fortunately, it's really easy to do what you want by introducing a new constraint annotation:
#Constraint(validatedBy = {}) // no validator needed, it delegates to other constraints
#NotNull
#Length(min = 4, max = 64)
#Pattern(regexp = "[A-Za-z0-9]+")
#ReportAsSingleViolation // to prevent separate violations for the other constraints
#Target(FIELD)
#Retention(RUNTIME)
public #interface ValidUsername {
String message() default """
{jakarta.validation.constraints.NotNull.message}
AND {org.hibernate.validator.constraints.Length.message}
AND {jakarta.validation.constraints.Pattern.message}""";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This is annotated with your constraints, which means these are applied when this annotation is checked.
The message will still contain place holders {min} etc., because these are not properties of this constraint itself. You can solve that in two ways:
Don't use the templates but hard-code the message
Add these properties to your annotation as well, and tell the Bean Validation framework to use these for the other annotations:
#OverridesAttribute(constraint = Length.class)
int min() default 4;
#OverridesAttribute(constraint = Length.class)
int max() default 64;
#OverridesAttribute(constraint = Pattern.class)
String regexp() default "[A-Za-z0-9]+";
Now all you need to do is replace the annotations on the username field with #ValidUsername.
I need to call a PUT or POST request with a payload
{
name:"dummy",
marks:"90.50"
}
And I have the following entity:
#Entity(name="Student")
public class Student {
private String name;
#Min(value = 0, message = "Marks should be positive.")
#Digits(integer = 2, fraction = 2, message = "value must be positive and 2 digits whole number")
private Float marks;
}
If I call API with marks as negative it gives me an appropriate error saying "Marks should be positive." because of the #Max annotation.
Similar to it, how can I show an appropriate message if I call API with invalid marks like "90.abc" or "abcd".
PS: I already have validation on the frontend, but need validation in the backend as well.
I´d say your marks property is already a Float. So how should it be possible to pass a String to this property? Isn´t it possible to directly design the API to have a number type for the JSON value type of marks?
If you really need the validation if a String is a number, it can be done with a #Pattern, e.g. #Pattern(regexp = "^\\d+(?:\\.\\d+)?\$").
Say I have validation on a field like this:
#NotEmpty
#Digits(integer = 3, fraction = 0)
private String code;
Using Spring MVC and Hibernate validation currently I get both messages if I leave the form field blank. Is there a way to only display the #NotEmpty message?
If you want to stick to the Bean Validation Specification you need to use a group sequence. Only a groups sequence guarantees an ordered constraint evaluation which stops on the first error. Something like:
#GroupSequence({ First.class, Second.class })
public class MyBean {
#NotEmpty(groups = First.class)
#Digits(integer = 3, fraction = 0, groups = Second.class)
private String code;
}
#NotNull(message = "{email.error}")
#Valid(message = "{email.error}")
private String email;
#NotNull(message = "{password.error}")
#Size(min = 8, max = 16, message = "{password.error}")
private String password;
Is there anyway to group the constraints that belong to a single attribute?
So if one constraint is not passed, this will throw the error in question. If multiple constraints are not valid, this would still throw only this one error.
Let me illustrate what type I am after:
#List(constraints = {
#NotNull,
#Valid
}, message = "{email.error}")
private String email;
#List(constraints = {
#NotNull,
#Size(min = 8, max = 16)
}, message = "{password.error}")
private String password;
Is something like this provided or what I have to create my own Validator for this?
Thanks in advance.
You are probably looking for the group respectively group sequence feature. Check the documentation for that. There are heaps of examples.
Regarding your example, #Valid on a String does not make sense. This annotation is for cascaded validation. Again check the documentation if you want to know more. Also an annotation like #List does not exist and wold not even compile.
I use JSR-303 bean validation and Spring 3 and I need to provide different values for the annotation depending on the use-case.
For example, the value of min parameter in #Size(min=?) must be 1 for some validation and 5 for another case and I want to read this values from a properties file.
I know the message parameter can be read from ValidationMessages.properties file if provided as a key but what about the other parameter?
As outlined by dpb you can use validation groups to specify the same constraint with different attribute values.
If you're working with Hibernate Validator as BV implementation, based on that you could use the programmatic API instead of annotations to define your constraints. That way you could retrieve the concrete constraint values at runtime like this:
int minValue1 = ...; //read from properties file etc.
int minValue2 = ...;
//programmatically define the constraints for the Test type
ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Test.class )
.property( "prop", FIELD )
.constraint( new NotNullDef() )
.constraint( new SizeDef().min( minValue1 ).groups( GroupOne.class ) )
.constraint( new SizeDef().min( minValue2 ).groups( GroupTwo.class ) );
//retrieve a validator using the programmatic constraint mapping
HibernateValidatorConfiguration config =
Validation.byProvider( HibernateValidator.class ).configure();
config.addMapping( mapping );
ValidatorFactory factory = config.buildValidatorFactory();
Validator validator = factory.getValidator();
The values for annotation parameters can only be compile time expressions. This means that for the #Size(min=X, max=Z) X and Z must be resolvable at compile time.
Since min and max are declared as int on #Size, you are stuck.
If you need different values for min, I personally see two ways of doing it.
First, you could use a grouping on the validators. Use one group for min=1 and one group for min=5. For example, lets consider a Test class:
public class Test {
#NotNull
#Size.List({
#Size(min = 1, groups = GroupOne.class),
#Size(min = 5, groups = GroupTwo.class)
})
private String prop;
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
}
You must declare the groups:
public interface GroupOne {}
public interface GroupTwo {}
Then create some testing object plus the validator to go with it:
Test test = new Test();
test.setProp("XY");
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Then validate using the groups:
Set<ConstraintViolation<Test>> resultOne = validator.validate(test, GroupOne.class);
Set<ConstraintViolation<Test>> resultTwo = validator.validate(test, GroupTwo.class);
First case is valid since min=1 and "XY".length() == 2 but second will fail because min=5.
This method involves doing the validation manually and I don't think you can just rely on #Valid on a #RequestMapping annotated method to do the validation (since #Valid is just a trigger for the validation with no way of mentioning the required group for validation). Luckly Spring is very flexible and it won't be much overhead to call the validator yourself.
The second option I see involves creating your own validation annotation with a custom validator to match. Here you can find a simple example to get you started. With this solution you can declare min and max as string keys that your validator will resolve in the bundles prior to validation. This solution though is more overhead than the first.