I have a Spring Boot application, which uses annotations from javax.validation to validate my domain object. Unfortunately, the error message doesn't contain the ID of the domain object. I need the ID for operation and development (debugging).
Code
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Data
static class Model {
private String id;
#Min(2)
private int number;
}
#RestController
static class Controller {
#PostMapping("/test")
public int test(#RequestBody #Valid Model model) {
return 1;
}
}
}
Logs
2022-05-12 10:39:15.252 WARN 1424 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public int test.TestApplication$Controller.test(test.TestApplication$Model): [Field error in object 'model' on field 'number': rejected value [1]; codes [Min.model.number,Min.number,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [model.number,number]; arguments []; default message [number],2]; default message [muss größer-gleich 2 sein]] ]
Research
I can add values of the annotation attributes and the value of the validated property, see Hibernate Validator:
4.1.2. Interpolation with message expressions
As of Hibernate Validator 5 (Bean Validation 1.1) it is possible to use the Unified Expression Language (as defined by JSR 341) in constraint violation messages. This allows to define error messages based on conditional logic and also enables advanced formatting options. The validation engine makes the following objects available in the EL context:
the attribute values of the constraint mapped to the attribute names
the currently validated value (property, bean, method parameter etc.) under the name validatedValue
a bean mapped to the name formatter exposing the var-arg method format(String format, Object... args) which behaves like java.util.Formatter.format(String format, Object... args).
I can write a custom message interpolation, but the MessageInterpolator.Context only provides the validated value.
Question
How can I add domain object's ID to the validation message?
Related
Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true
I'm using Spring 5.1. I want to write a validator for a form I'm submitting and I would like to customize one of the error messages that comes back. I have the error message stored in a properties file ...
already.taken.registration.username=There is already an account created with the username {0}. Did you forget your password?
Note the "{0}" for where I would like to insert the invalid username the user has entered. So in my validator I have
import org.springframework.validation.Validator;
...
public class RegistrationFormValidator implements Validator
{
...
#Override
public void validate(final Object target, final Errors errors)
{
final RegistrationForm regForm = (RegistrationForm) target;
if (regForm != null &&
!StringUtils.isEmpty(regForm.getEmail()) &&
userWithEmail(regForm.getEmail()) ) {
errors.rejectValue("email", "already.taken.registration.username", regForm.getEmail());
} // if
However, when the specific branch is run, the {0} isn't getting populated, despite the fact I've verified that "regForm.getEmail()" is non-empty. I see this printed to my JSP page
There is already an account created with the username {0}. Did you forget your password?
What's the correct way to fill in the error message with custom data?
errors.rejectValue("email", "already.taken.registration.username", regForm.getEmail());
This will actually call the method
void rejectValue(#Nullable
java.lang.String field,
java.lang.String errorCode,
java.lang.String defaultMessage)
What you need is to add an array of objects with the arguments:
void rejectValue(#Nullable
java.lang.String field,
java.lang.String errorCode,
#Nullable
java.lang.Object[] errorArgs,
#Nullable
java.lang.String defaultMessage)
Your call will be something like this :
errors.rejectValue("email", "already.taken.registration.username", new Object[] {regForm.getEmail()}, null);
I am using the GWT Editor and javax validation.
I have a model bean with a child bean like so ->
public interface BeanA {
#Valid
BeanB getBeanB();
void setBeanB(BeanB b);
}
public interface BeanB {
#NotEmpty
public String getValue();
public void setValue(String value);
}
There is a Widget that implements the LeafValueEditor, HasEditorErrors interfaces.
The value seems to be binding with no issue.
public class MyWidget extends Composite implements
LeafValueEditor<String>, HasEditorErrors<String>{
...
#Override
public void showErrors(List<EditorError> errors) {
// Even though the error is flagged
// the errors collection does not contain it.
}
}
When I call validate and the widget getValue returns null, the ConstraintViolation collection contains the error but when showErrors is called, the List is empty.
Any idea why the violation is found but then does not make it to the widget showErrors?
If you're using GWT Editor, you would have SimpleBeanEditorDriver interface created by GWT.create(...). this interface has a method setConstraintViolations(violations) which gets your constraint violation from you. when you validate your Model, you would have Set<ConstraintViolation<E>> as violations, then you would pass this to your editor driver. for example
Set<ConstraintViolation<E>> violations = getValidator().validate(model,
groups);
getEditorDriver().setConstraintViolations(violations)
After this, you would get widget-specific errors on the widget's showErrors(List<EditorError> errors) method.
This appears to be a GWT 2.4 issue. I did the same example in GWT 2.5 and the path was correctly set and the error collection was correct.
I have a custom tag that extends Spring's InputTag to display numbers in the format ",###.0". I've registered a custom PropertyEditor for the Double class to handle the formatting.
When a form is submitted and validation fails, all invalid values should be re-displayed as-is, without any formatting, so that the user can see the mistake he made. How do I inform the custom tag of the validation result so that it does not do any formatting?
I'm using Spring MVC 3.
Thanks.
Override the getPropertyEditor() method of AbstractDataBoundFormElementTag, and return null instead of PropertyEditor instance (so the ValueFormatter will not pass the object value to PropertyEditor for formatting purpose).
public class CustomInputTag extends InputTag {
#Override
protected PropertyEditor getPropertyEditor() throws JspException {
if(getBindStatus().getErrors().hasErrors()) {
return null;
}
return super.getPropertyEditor();
}
}
I'm trying to set up a custom message source for Hibernate Validator 4.1 through Spring 3.0. I've set up the necessary configuration:
<!-- JSR-303 -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource"/>
</bean>
The translations are served from my message source, but it seems that the replacement tokens in the messages themselves are looked up in the message source, i.e. for a:
my.message=the property {prop} is invalid
there are calls to look up 'prop' in the messageSource. Going into ResourceBundleMessageInterpolator.interpolateMessage I note that the javadoc states:
Runs the message interpolation according to algorithm specified in JSR 303.
Note:
Look-ups in user bundles is recursive whereas look-ups in default bundle are not!
This looks to me like the recursion will always take place for a user-specified bundle, so in effect I can not translate standard messages like the one for Size.
How can I plug-in my own message source and be able to have parameters be replaced in the message?
This looks to me like the recursion
will always take place for a
user-specified bundle, so in effect I
can not translate standard messages
like the one for Size.
Hibernate Validator's ResourceBundleMessageInterpolator create two instances of ResourceBundleLocator (i.e. PlatformResourceBundleLocator) one for UserDefined validation messages - userResourceBundleLocator and the other for JSR-303 Standard validation messages - defaultResourceBundleLocator.
Any text that appears within two curly braces e.g. {someText} in the message is treated as replacementToken. ResourceBundleMessageInterpolator tries to find the matching value which can replace the replacementToken in ResourceBundleLocators.
first in UserDefinedValidationMessages (which is recursive),
then in DefaultValidationMessages (which is NOT recursive).
So, if you put a Standard JSR-303 message in custom ResourceBundle say, validation_erros.properties, it will be replaced by your custom message. See in this EXAMPLE Standard NotNull validation message 'may not be null' has been replaced by custom 'MyNotNullMessage' message.
How can I plug-in my own message
source and be able to have parameters
be replaced in the message?
my.message=the property {prop} is
invalid
After going through both ResourceBundleLocators, ResourceBundleMessageInterpolator finds for more replaceTokens in the resolvedMessage (resolved by both bundles). These replacementToken are nothing but the names of Annotation's attributes, if such replaceTokens are found in the resolvedMessage, they are replaced by the values of matching Annotation attributes.
ResourceBundleMessageInterpolator.java [Line 168, 4.1.0.Final]
resolvedMessage = replaceAnnotationAttributes( resolvedMessage, annotationParameters );
Providing an example to replace {prop} with custom value, I hope it will help you....
MyNotNull.java
#Constraint(validatedBy = {MyNotNullValidator.class})
public #interface MyNotNull {
String propertyName(); //Annotation Attribute Name
String message() default "{myNotNull}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
MyNotNullValidator.java
public class MyNotNullValidator implements ConstraintValidator<MyNotNull, Object> {
public void initialize(MyNotNull parameters) {
}
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
return object != null;
}
}
User.java
class User {
private String userName;
/* whatever name you provide as propertyName will replace {propertyName} in resource bundle */
// Annotation Attribute Value
#MyNotNull(propertyName="userName")
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
validation_errors.properties
notNull={propertyName} cannot be null
Test
public void test() {
LocalValidatorFactoryBean factory = applicationContext.getBean("validator", LocalValidatorFactoryBean.class);
Validator validator = factory.getValidator();
User user = new User("James", "Bond");
user.setUserName(null);
Set<ConstraintViolation<User>> violations = validator.validate(user);
for(ConstraintViolation<User> violation : violations) {
System.out.println("Custom Message:- " + violation.getMessage());
}
}
Output
Custom Message:- userName cannot be null