Validate a RESTful web service request in spring

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.