Rinat Mukhamedgaliev Rinat Mukhamedgaliev - 3 months ago 51
Java Question

Spring 4 and Hibernate 5 method argument validation

How validate incoming arguments with Hibernate ?

In XML

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
</bean>


In Java

@Validated
public class UserService

@Override
@NotNull
public User registerUser(@NotEmpty String name, String username, String password, boolean google, boolean facebook)


This approach not work i call method with error params and validation not work.

Answer

Question Clarification

When using Hibernate Validator (HV) 4.2 with Spring-MVC 4, e.g. with dependencies:

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
    compile 'org.springframework:spring-webmvc:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:4.2.0.Final'
    runtime 'javax.servlet:jstl:1.1.2'
}

one can tell Spring to automatically use HV to validate method calls of classes annotated with @Validated (as in asker's example) by declaring following beans in the configuration file,

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

When the target's method get called and has somethings that violates its constraints, the MethodConstraintViolationException is thrown. For example, something like this will be raised if one happeded to call UserService#registerUser(...), passing an empty String to where annotated with @NotEmpty

... The following constraint violations occurred: [MethodConstraintViolationImpl
[method=public abstract void foo.UserService.registerUser(java.lang.String),
parameterIndex=0, parameterName=arg0, kind=PARAMETER, message=may not be empty, 
messageTemplate={org.hibernate.validator.constraints.NotEmpty.message}, ...

But the problem is if you change the dependencies from above to

dependecies {
    providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
    compile 'org.springframework:spring-webmvc:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:5.1.0.Final' //**note the change here**
    runtime 'javax.servlet:jstl:1.1.2'
}

then you will get an error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name
'org.springframework.validation.beanvalidation.MethodValidationPostProcessor#0'
defined in class path resource [spring/config/web/main-config.xml]: Invocation of init
method failed; nested exception is javax.validation.ValidationException: Unable to
instantiate Configuration.

Solution

Add 2 additional dependecies to the classpath as follows,

dependecies {
    providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
    providedCompile 'javax.el:el-api:2.2'
    providedCompile 'org.glassfish.web:el-impl:2.2'
    compile 'org.springframework:spring-webmvc:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:5.1.0.Final' //**note the change here**
    runtime 'javax.servlet:jstl:1.1.2'
}

And the validation will comeback to life. But this time, instead of MethodConstraintViolationException, it raises ConstranitViolationException with no details of what has been violated.

I am not sure how Spring manage this internally and wheter you can unfold the violation details out of this Exception. HV 5 no longer supports MethodConstraintViolationException and its relatives, e.g. MethodValidator, MethodConstraintViolation. This is something to do with Bean Validation 1.1 specification, which has some changes in method-level validation from BV 1.0. I suggest creating the validation service yourself using aspect and with ExecutableValidator (instead of the old MethodValidator) as explained in HV's Reference guide.

Note that you if you try to obtain the validator like this,

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();

you may get ValidationException: Unable to instantiate configuration. I can solve this problem by do this

ValidatorFactory factory = Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
Comments