Part 4: Adding Validation

In a series of blogs we will look at how to create a REST service using the latest version of Spring. Our service will have validation so we can make sure the data stored meets the requirements and it will be secured so only the correct people can make updates.

In the previous entry we went into detail on REST and how to use Spring 4.0 to create a RESTful service. In this entry we will look at adding validation using JSR-303 and Spring's built in support for validation.

JSR-303

This is a specification of how to handle the common task of validating data. This is an API that is implemented by different providers, the most commonly used one is Hibernate Validator which is what we will be using. JSR-303 defines many common validators like @NotNull, @Min, @Max, @Pattern, etc. When the common ones are not enough it is easy to add custom validators.

Configuring the Validation

Spring will set this up automatically out of the box as long as the correct jars are on the classpath but in our example we are going to customize the behavior a little bit.

The way to customize it is to override the getValidator() method of our WebConfig from part 2 in this series.

 @Autowired
 private MessageSource messageSource;
 
 @Bean
 public LocalValidatorFactoryBean validator() {
 LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
 validatorFactoryBean.setValidationMessageSource(messageSource);
 return validatorFactoryBean;
 }
 
 @Override
 public Validator getValidator() {
 return validator();
 }

The autowired message source is configure elsewhere but it is needed for getting our validator to return better error messages than the built in ones.

Adding the Validation Annotations

In our application we map our requests to a data transport layer before updating our persistence entities. We need the data transport layer to be able to have validation on the input data that might not be there for the persistence layer. For example the client needs to send the package number twice for confirmation but we don't want to have package number twice in our database. Let's add validation constraints to our data transport objects.

@MatchingPackageNumbers
public class TicketVO {
 
 private Long id;
 
 @Alphabetic
 private String firstName;
 
 @Alphabetic
 private String lastName;
 
 @Valid
 private AddressVO address;
 
 @PhoneNumber
 private String phoneNumber;
 
 @PackageNumber
 private String packageNumber;
 
 private String confirmPackageNumber;
 
 ...
}

Most of these validation constraints are custom constraints. Lets look at how to that by looking at the @Alphabetic constraint as an example.

@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@Pattern(regexp = "^[A-Za-z -]+$", message = "{error.validation.not_alphabetic}")
public @interface Alphabetic {
 
}

The same validation can be done by adding only the @Pattern annotation from our custom annotation directly to the object but this encapsulates it and makes it more readable.
The address field is annotated with a @Valid which triggers validation of the associated object Address which has it's own constraints.

Let's look at a more complicated validator, the @MatchingPackageNumbers.

@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = MatchingPackageNumbers.MatchingPackageNumbersValidator.class)
@Documented
public @interface MatchingPackageNumbers {
 
 String message() default "{error.validation.package_numbers_do_not_match}";
 
 Class<??>[] groups() default {};
 
 Class<> extends Payload>[] payload() default {};
 
 public static class MatchingPackageNumbersValidator implements ConstraintValidatorMatchingPackageNumbers, TicketVO> {
 
 private MatchingPackageNumbers matchingPackageNumbers;
 
 @Override
 public void initialize(MatchingPackageNumbers matchingPackageNumbers) {
 this.matchingPackageNumbers = matchingPackageNumbers;
 }
 
 @Override
 public boolean isValid(TicketVO ticket, ConstraintValidatorContext constraintValidatorContext) {
 
 if (StringUtils.isBlank(ticket.getPackageNumber()) && StringUtils.isBlank(ticket.getConfirmPackageNumber())) {
 return true;
 }
 
 if (ticket.getPackageNumber().equals(ticket.getConfirmPackageNumber())) {
 return true;
 }
 
 constraintValidatorContext.disableDefaultConstraintViolation();
 constraintValidatorContext.buildConstraintViolationWithTemplate(matchingPackageNumbers.message()).addConstraintViolation();
 return false;
 }
 
 }
}

This constraint uses a custom validator by adding the @Constraint annotation and pointing out a class that is used to validate it. All you need to do after that is implement the interface ConstraintValidator.

A good practice I would recommend is to create validators that only run if there is a value passed in. This means that an empty or null value is legal according to this validator and that is ok because we can combine it with a @NotNull annotation if needed. This lets us do partial updates of our objects.
The last step in our validator is to add the violation and an error message. We grab this from the instance of our validation constraint instance. If we want to use a different message key in some case we can do that, if not it uses the default specified for the value above.

Triggering Validation

All that is needed for Spring to trigger the validation is to add a @Valid annotation to the input of any @RequestMapping method. Here is our changed get method for our TicketFacade.

 @RequestMapping(value = "/{ticketId}", method = RequestMethod.PATCH)
 public TicketVO update(@PathVariable long ticketId, @Valid @RequestBody TicketVO ticket) {
 Ticket previouslyPersisted = ticketService.get(ticketId);
 mappingService.map(ticket, previouslyPersisted);
 Ticket persisted = ticketService.store(previouslyPersisted);
 return mappingService.map(persisted, TicketVO.class);
 }

The simple addition of @Valid to our request parameter will run any validation from the class TicketVO on our instance. The validator will also validate any type that is part of the TicketVO as long as they have a @Valid annotation as has been done for the AddressVO above.

Error Handling

In the previous entry we added @ControllerAdvice and mapped our exceptions to JSON responses. Now we are going to add mappings for our data validation exceptions.

 @RequestMapping(produces = {Versions.V1_0, Versions.V2_0})
 @ExceptionHandler(MethodArgumentNotValidException.class)
 @ResponseStatus(value = HttpStatus.BAD_REQUEST)
 public @ResponseBody MapString, Object> handleValidationException(MethodArgumentNotValidException ex) throws IOException {
 MapString, Object> map = Maps.newHashMap();
 map.put("error", "Validation Failure");
 map.put("violations", convertConstraintViolation(ex));
 return map;
 }
 
 private MapString, MapString, Object> > convertConstraintViolation(MethodArgumentNotValidException ex) {
 MapString, MapString, Object> > result = Maps.newHashMap();
 for (ObjectError error : ex.getBindingResult().getAllErrors()) {
 MapString, Object> violationMap = Maps.newHashMap();
 violationMap.put("target", ex.getBindingResult().getTarget());
 violationMap.put("type", ex.getBindingResult().getTarget().getClass());
 violationMap.put("message", error.getDefaultMessage());
 result.put(error.getObjectName(), violationMap);
 }
 return result;
 }

What this does is convert the exception into a map that we return along with a 400 (Bad Request) so that the calling client will get some information about what was wrong. The message will contain a plain language error message taken from our MessageSource we configured earlier. Depending on the the "Accept-Language" header it will be in different languages but this is not covered in this blog.

Conclusion

We now have automatic validation of our data transport layer that will protect our persisted data. As we add more services or more versions of the services the validation is built into the data transport layer and not something we have to copy from service to service.

Each part of this blog will add functionality to our application.
Part 1: Creating the Basic Application
Part 2: Configuring The Application
Part 3: Adding the REST Services
Part 4: Adding Validation
Part 5: Adding Security to the Services
Part 6: Customising the Security for REST

Next we will look at securing our REST service using Spring Security 3.2.

The code examples are all heavily edited excerpts but the complete source code can be found on GitHub.