I am building a REST API. Its made up of a Resource ( @Controller ) which returns a response 204 even when one of the mandatory field is not present.
I am using Spring 3.1, validation-api (1.1.0.Final) & Hibernate-validator(4.3.0). Not sure if Hibernate-validator plays any role here.
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
<exclusions>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
I have a spring controller @Controller and a Bean with @Component
@POST
@Consumes(value = {MediaType.APPLICATION_JSON})
@Produces(value = {MediaType.APPLICATION_JSON})
@RequestMapping(method = RequestMethod.POST)
public Response addUserData(@Valid @RequestBody UserData userDataRequest) throws ResourceException {
...
}
My UserData bean has
@Component
public class UserData {
@NotNull(message = "user ID should not be null")
@Min(value = 1, message = "user ID should not be empty")
@Max(value = 20, message = "user ID should have length of more than 20")
@Pattern(regexp="[A-Z][a-z]+", message = "Only Alphanumeric characters allowed")
private String userId;
}
My validations are not getting executed. When I dont pass "userId", there is no error thrown. What am I missing here ?
You must have the following about the infrastructure
@EnableWebMvc
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
@Autowired
private ValidatorConfig validatorConfig;
...
@Override
public Validator getValidator() {
return validatorConfig.localValidatorFactoryBean();
}
...
}
Where validatorConfig
comes from
@Configuration
public class ValidatorConfig {
@Autowired
private ReloadableResourceBundleMessageSource rrbms;
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(){
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(rrbms);
return localValidatorFactoryBean;
}
}
And finally (I suggest you consider put the error messages in a .properties
file, such as validation.properties
how shown below)
@Configuration
public class MessageSourceConfig {
@Bean(name="messageSource")
public ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource() {
ReloadableResourceBundleMessageSource resource = new ReloadableResourceBundleMessageSource();
resource.setBasenames("classpath:/com/manuel/jordan/somethinga",
"classpath:/com/manuel/jordan/validation/validation");
resource.setDefaultEncoding("UTF-8");
return resource;
}
}
Some considerations or suggestions:
- change
@Valid
to @Validated
(see the API for the @Validated)
- Remove the
@Component
for UserData
(that represents a kind of entity right?). Remember that for that class each instance is unique and any bean
managed by Spring is Singleton.
- put the error messages in a
.properties
file
- from where come the
@POST
, @Consumes
and @Produces
annotations?. They are not in the Spring API
Addition 01 about your comment:
Yes, you must use @EnableWebMVC
. It indicates to Spring create some special beans internally for the web environment. See the @EnableWebMVC API. Is very important that annotation. Even for Rest I use that annotation.
About the Rest annotations, use the Spring annotations. Such as @RequestMapping
and new 'variations' such as @GetMapping
, @PostMapping
etc.. That annotations contain the produces
and consumes
attributes. I have never seen your approach about mixing two sets of annotations from Rest.
Addition 02
The WebMvcConfigurerAdapter
class represents the XML configuration file about all the Spring MVC
infrastructure
Therefore for XML
@EnableWebMvc
is equivalent <mvc:annotation-driven/>
- About validation it should be:
<mvc:annotation-driven validator="beanNameValidator" />
where the validator
attribute according with the .xsd
says:
Attribute : validator The bean name of the Validator that is to be
used to validate Controller model objects. This attribute is not
required, and only needs to be specified explicitly if a custom
Validator needs to be configured. If not specified, JSR-303
validation will be installed if a JSR-303 provider is present on the
classpath.
beanNameValidator
according with my @Bean
seems should be localValidatorFactoryBean
I ultimately ended up using Jersey Bean Validation, instead of Spring. This is because rest of my code is using Jersey anyways. To make this work I just imported Jersey Bean Validation jar and added a small change to web.xml. Validations are now working.
Thank you @Manual Jordan. I will upvote your answer, since it gave me the right clues.
<!-- jersey beanValidation -->
<init-param>
<param-name>jersey.config.beanValidation.enableOutputValidationErrorEntity.server</param-name>
<param-value>true</param-value>
</init-param>
Maybe passing to your "addUserData" method, a bindingResult Object, so you can test for and retrieve validation errors.
here is an example of how to use it : Validation form input