Find out how to validate a RESTFul request when posting to a spring controller using the bean validation API and spring's validator interface.
Detailed Video Notes
When making a post to a RESTFul webservice it is a good practice to trigger client side validation providing immediate feedback to users and apply secure coding practices by performing server side validation early in the request process. This tutorial will focus on how to validate your REST webservice request with the spring framework.
Getting started
Spring framework has the flexibility to be configured with both JSR-303, JSR-349 bean validation or by implementing spring Validator
interface to meet your organizations choice of validation. We will set up a spring boot project where boot does it's magic and turns on validation automatically for us. By examining actuator endpoint http://localhost:8080/beans
, OptionalValidatorFactoryBean
is declared as the mvcValidator
and uses hibernate validator implementation. If you are setting up validation outside of boot you will need to include pom entry and include mvc:annotation-driven
in your configuration file. Further configuration can be reviewed at spring's validation documentation.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- JSR-303/JSR-349 support will be detected
on classpath and enabled automatically -->
<mvc:annotation-driven/>
</beans>
Project setup
[1:3]
We will copy files from episode 10 where we showed how to get started building rest service and modify the AgencyController class by adding method = RequestMethod.GET
to the @RequestMapping
. Creating a new method saveAgency()
and applying @RequestMapping(value = "/agencies", method = RequestMethod.POST)
to handle the post request. You will notice that both saveAgency
and getAgencies
have the same URL mapping. The differentiator is the method equals in the @RequestMapping which instructs spring to map a POST request to this method. If you recall @RestController
was introduced in Spring 4 and combines the @Controller
and a @ResponseBody
eliminating the need to wrap each method.
AgencyController
@RestController
public class AgencyController {
//...
@RequestMapping(value = "/agencies", method = RequestMethod.POST)
public ResponseEntity<AgencyResource> saveAgency() {
System.out.println(agencyResource.getName());
return new ResponseEntity<AgencyResource>(agencyResource, HttpStatus.OK);
}
//...
}
AgencyResource
public class AgencyResource {
private Integer id;
private String name;
private String EIN;
}
Using JSR-303/JSR-349 Bean Validation API
[1:40]
Adding @Valid @RequestBody to controller
JSR-303 is a standardization on java bean validation while JSR-349 improves on the initial version. The @Valid
annotation is part of java bean validation API and is not a spring-specific construct. By adding it to the input parameter within a method in @Controller
we will trigger validation. Adding the @RequestBody
will indicate the annotated method parameter should be generated from the body of the HTTP request. In other words, spring will use jackson to transform json from the body of the request to a java object. Let's modify the AgencyResouce object with some standard self explanatory javax validation constraints.
public class AgencyResource {
@NotNull
@Min(1)
@Max(20)
private Integer id;
@NotNull
private String name;
@NotNull
private String EIN;
//..
}
Next we will need to add the @Valid
and @RequestBody
annotation to our method parameters. By adding these two annotations we have instructed spring to bind JSON from the body of the POST to the AgencyResource and then run validation.
@RequestMapping(value = "/agencies", method = RequestMethod.POST)
public ResponseEntity<AgencyResource> saveAgency(
@Valid @RequestBody AgencyResource agencyResource) {
System.out.println(agencyResource.getName());
//save to DB or ?
return new ResponseEntity<AgencyResource>(agencyResource, HttpStatus.OK);
}
Triggering validation with a POST
Making a POST to http://localhost:8080/agencies
using advanced rest client with the following json will cause the a 404. The response indicates that the field "name" cannot be null and the "id" as rejected and must be greater or equal to 20.
Request JSON
{
"id": 50,
"ein": "ABC123"
}
Response
{"timestamp":1417379464584,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<demo.AgencyResource> demo.AgencyController.saveAgency(demo.AgencyResource), with 2 error(s): [Field error in object 'agencyResource' on field 'name': rejected value [null]; codes [NotNull.agencyResource.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [agencyResource.name,name]; arguments []; default message [name]]; default message [may not be null]] [Field error in object 'agencyResource' on field 'id': rejected value [50]; codes [Max.agencyResource.id,Max.id,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [agencyResource.id,id]; arguments []; default message [id],20]; default message [must be less than or equal to 20]] ","path":"/agencies"}
Using spring’s validator interface
[2:29]
To demonstrate using the spring validator interface we will add two new classes and modify the controller.
Policy support classes
First adding a class PolicyResource that represents the policy domain. Notice that there is no java validation constraints on the fields. We will create a PolicyValidator class that extends org.springframework.validation.Validator
interface. In the validate method calling ValidationUtils.rejectIfEmpty
will create an error if name is empty.
public class PolicyResource {
private String name;
//..
}
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class PolicyValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return PolicyResource.class.equals(clazz);
}
@Override
public void validate(Object target, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
PolicyResource p = (PolicyResource) target;
//perform additional checks
//if name already exists or ?
}
}
Modifying the controller
In the controller we first need to notify spring's DataBinder that we have a validator to run when binding to a request parameter. Second we will add the url mapping and method to handle the request that looks structurally similar to the JSR-303 implementation above. If this was production code we probably would of created a separate controller to handle policy requests.
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new PolicyValidator());
}
@RequestMapping(value = "/policies", method = RequestMethod.POST)
public ResponseEntity<PolicyResource> savePolicies(
@Valid @RequestBody PolicyResource policyResource) {
System.out.println(policyResource.getName());
return new ResponseEntity<PolicyResource>(policyResource, HttpStatus.OK);
}
Triggering validation with a request
Making a request to http://localhost:8080/policies
with the following JSON as the request body will trigger a validation error stating the name cannot be empty.
Request JSON
{
"name": ""
}
Response
{"timestamp":1417381110983,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<demo.PolicyResource> demo.AgencyController.savePolicies(demo.PolicyResource), with 1 error(s): [Field error in object 'policyResource' on field 'name': rejected value []; codes [name.empty.policyResource.name,name.empty.name,name.empty.java.lang.String,name.empty]; arguments []; default message [null]] ","path":"/policies"}
We didn't get into specific ways to handle the exception within your spring rest application but hope that this will give a high level way to handle validation within your RESTFul controllers. Thanks for joining today's level up lunch.