Bean Validation

Data validation is not a new topic in web application development and here we take a brief look at data validation in Java ecosystem in general and Spring Framework specifically. Java platform has de facto a standard for implementing data validation that is Bean Validation specification. Bean Validation spec has several versions:

  • 1.0 (JSR-303),
  • 1.1 (JSR-349),
  • 2.0 (JSR 380) – the latest version

This spec defines set of components, interfaces, annotations to provide a standard way to put constraints to the parameters and return values of methods and parameters of constructors, provide API to validate objects and object graphs. Declarative model is used to put constraints in the form of annotations on objects and their fields. There are predefined annotations like @NotNull, @Digits, @Pattern, @Email, @CreditCard, etc. and there is an ability to create new custom constraints. Validation could be run manually or in a more natural way, when other specs and frameworks validate data at the right time, for example, user input, insert or update in JPA, etc.

Validation in Java Example

Let’s take a look at how it could be done in practice in this simple Bean Validation example inside regular Java application.

We have an object that we want to validate with all fields annotated with constraints.

public class SimpleDto {

@Min(value = 1, message = "Id can't be less than 1 or bigger than 999999")
@Max(999999)
private int id;

@Size(max = 100)
private String name;

@NotNull
private Boolean active;

@NotNull
private Date createdDatetime;

@Pattern(regexp = "^asc|desc$")
private String order = "asc";

@ValidCategory(categoryType="simpleDto")
private String category;

Constructor, getters and setters

Here is an example; @Min, @Max, @Size, @NotNull, @Pattern are standard annotations, and @ValidCategory is custom.

Now we can use it in a simple java application and manually validate object.

public class SimpleApplication {

public static void main(String[] args) {

final SimpleDto simpleDto = new SimpleDto();
simpleDto.setId(-1);
simpleDto.setName("Test Name");
simpleDto.setCategory("simple");
simpleDto.setActive(true);
simpleDto.setOrder("asc");
simpleDto.setCreatedDatetime(new Date());

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.usingContext().getValidator();

Set<ConstraintViolation> constrains = validator.validate(simpleDto);
for (ConstraintViolation constrain : constrains) {

System.out.println(
"[" + constrain.getPropertyPath() + "][" + constrain.getMessage() + "]"
);

}

}

}

And result in Console will be:
“[id] [Id can't be less than 1 or bigger than 999999]”

Here is how we create a custom validation constraint and annotation, @ValidCategory in this example

Annotation:

@Retention(RUNTIME)
@Target(FIELD)
@Constraint(validatedBy = {ValidCategoryValidator.class})
public @interface ValidCategory {

String categoryType();

String message() default "Category is not valid";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}

And constraint validation implementation:

public class ValidCategoryValidator implements ConstraintValidator<ValidCategory, String> {

private static final Map<String, List> availableCategories;

static {

availableCategories = new HashMap<>();
availableCategories.put("simpleDto", Arrays.asList("simple", "advanced"));

}

private String categoryType;

@Override
public void initialize(ValidCategory constraintAnnotation) {

this.setCategoryType(constraintAnnotation.categoryType());

}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {

List categories = ValidCategoryValidator.availableCategories.get(categoryType);
if (categories == null || categories.isEmpty()) {

return false;

}

for (String category : categories) {

if (category.equals(value)) {

return true;

}

}

return false;

}

}

In this example available categories come from simple hash map when in real application they can be retrieved from database or any other services.

Please note that constraints and validations could be specified and performed not only on field level but also on an entire object when we need to validate various field dependencies, for example, like start date, can’t be after the end date, etc.

The most widely used implementations of Bean Validation spec are Hibernate Validator and Apache BVal.

Validation With Spring

Spring framework provides several features to validation support.

  • Support for Bean Validation API versions 1.0, 1.1 (JSR-303, JSR-349) was introduced in Spring Framework starting with version 3.
  • Spring has its own Validator interface that is very basic and can be set in specific DataBinder instance. This could be useful for implementing validation logic without annotations.

Bean Validation With Spring

Spring Boot provides validation started which can be included in the project:

<dependency>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>

</dependency>

This starter provides a version of Hibernate Validator compatible with current Spring Boot.

Using Bean Validation we could validate for example request body, query parameters, variables within the path (e.g. / /simpledto/{id}), or any method or constructor parameters.

POST or PUT requests

In POST or PUT requests, for example, we pass JSON payload, Spring automatically converts it into Java object and now we want to validate resulting object. Let’s use SimpleDto object from the 1st example:

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

@Autowired
private SimpleDtoService simpleDtoService;

@RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
public SimpleDto createSimpleDto(

@Valid @RequestBody SimpleDto simpleDto) {

SimpleDto result = simpleDtoService.save(simpleDto);

return result;

}

}

We just added @Valid annotation to the SimpleDto parameter annotated with @RequestBody. This will tell Spring to process validation before making actual method call. In case validation is failed Spring will throw MethodArgument NotValidException which by default will case 400 (Bad Request response).

Validating Path Variables

Validating Path Variables works a little differently. The problem is that now we have to add constraint annotations directly to method parameters instead of inside of objects. To make this work there are 2 possible solutions:

Validated Annotation

Add @Validated annotation to the controller at class level to evaluate constraint annotations on method parameters.

Path Variable

Use an object that represents Path Variable like in this example:

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

@Autowired
private SimpleDtoService simpleDtoService;

@RequestMapping(path = "/{simpleDtoId}", method = RequestMethod.GET, produces = "application/json")
public SimpleDto getSimpleDto(

@Valid SimpleDtoIdParam simpleDtoIdParam) {

SimpleDto result = simpleDtoService.findById(simpleDtoIdParam.getSimpleDtoId());

if (result == null) {

throw new NotFoundException();

}

return result;

}

}

In this case, we have SimpleDtoIdParam class that contains simpleDtoId field which will be validated against a standard or custom Bean constraint annotations. Path Variable (/{simpleDtoId}) name should be the same as the field name (so Spring will be able to find setter for this field)

private static final long serialVersionUID = -8165488655725668928L;

@Min(value = 1)
@Max(999999)
private int simpleDtoId;

public int getSimpleDtoId() {
return simpleDtoId;
}

public void setSimpleDtoId(int simpleDtoId) {
this.simpleDtoId = simpleDtoId;
}

}

And in contrast to Request Body validation Path Variable validation throws ConstraintViolationException instead of MethodArgumentNotValidException. Thus we would need to create a custom exception handler.

Spring framework also allows validating parameters on a service level with @Validated annotation (class level) and @Valid (parameter level). And since Spring JPA is using Hibernate underneath it supports Bean Validation for entity classes as well. Please note that it’s probably not a good idea in many cases rely on this level of validation since it means that all logic before was dealing with invalid objects.

Spring Validation Interface

Spring defines its own interface for validation Validator (org.springframework.validation.Validator). It can be set for specific DataBinder instance and implements validation without annotations (not declarative approach).

To implement this approach we would need to:

Implement Validator Interface

Implement Validator interface, for example lets work with our SimpleDto class

@Component
public class SpringSimpleDtoValidator implements Validator {

@Override
public boolean supports(Class<?> clazz) {
return SimpleDto.class.isAssignableFrom(clazz);
}

@Override
public void validate(Object target, Errors errors) {

if (errors.getErrorCount() == 0) {

SimpleDto param = (SimpleDto) target;
Date now = new Date();
if (param.getCreatedDatetime() == null) {

errors.reject("100",

"Create Date Time can't be null");

} else if (now.before(param.getCreatedDatetime())) {

errors.reject("101",

"Create Date Time can't be after current date time");

}

}

}

}

We check here if created date time is in the future.

Add Validator

Add Validator implementation to DataBinder:

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

@Autowired
private SimpleDtoService simpleDtoService;

@Autowired
private SpringSimpleDtoValidator springSimpleDtoValidator;

@InitBinder("simpleDto")
public void initMerchantOnlyBinder(WebDataBinder binder) {
binder.addValidators(springSimpleDtoValidator);
}

@RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
public SimpleDto createSimpleDto(

@Valid @RequestBody SimpleDto simpleDto) {

SimpleDto result = simpleDtoService.save(simpleDto);
return result;

}

}

Now we have SimpleDto validated using constraint annotations and our custom Spring Validation implementation.

Conclusion

Here we briefly touched Java platform Bean Validation specification, some of its implementation and how this spec is supported in Spring Framework, plus Spring’s Validation Interface. Here is github respository with code examples and some useful links:

Bean Validation official website: https://beanvalidation.org/

Hibernate Validator: http://hibernate.org/validator/

Apache BVal: http://bval.apache.org/

Spring: https://spring.io/