I have a bean under validation by the Hibernate Validator.
That bean has a property with List
type, and this property has custom validator (1). Elements of this List
also have their own custom validators (2).
I need the elements of this list to be validated first, and only then, when validity of all list elements has been ensured, the whole list must be validated.
The difficulty is also in that the list is polymorphic and contains elements of different types, all are descendants of the one base type, and each descendant has their own custom validator.
Code example below (simplified):
// Validation root
class Bean(
// List validator (1)
@field:ValidList
val list: List<BaseType>
)
// Base type for list elements (has no validator)
interface BaseType
// Descendant 1 of the BaseType with custom validator (2.1)
@ValidA
class A : BaseType
// Descendant 2 of the BaseType with custom validator (2.2)
@ValidB
class B : BaseType
By the code example above, I need the following validation order:
@ValidA
@ValidB
@field:ValidList
The low-level validators on classes A
and B
must ensure the isolated validity of the objects A
and B
, whereas top-level list validator must validate elements of the list against each other.
By the time to validate the elements of the list relative to each other, each separate element must already be valid. Otherwise, the list validation, being performed first, will not make sense and may produce weird senseless violations if individual elements of the list are not valid.
Is there possibility to reach such behavior with Hibernate Validator?
I tried to solve my problem using @GroupSequence
, but I can't determine how to use it correctly in my case.
Also I discovered that when I use annotation @Valid
above the List
field, Hibernate Validator always validates this List
first, and only then validates inner objects, but if I don't use @Valid
and use some annotation right inside the collection's generic like List<@ValidSome BaseType
>, Hibernate Validator, in countrary, validates inner objects first (exactly what I want to achieve), but in this case it ignores the hierarchy and doesn't validate A
and B
by their annotations at all.
Okay, it seems like I've solved the problem. Indeed validation groups do the job, and fortunately even without using @GroupSequence
.
I have created a number of validation group interfaces and defined a desired order of them with which Hibernate Validator must validate my bean. Then I have placed my groups in annotations with the corresponding order of applying. And after that all that remains to do is to loop through the ordered group list and invoke Hibernate Validator with each group separately (checking whether validation with the next group has failed and breaking the loop if so).
Groups definition and their usage in the validation annotations:
object ValidationGroups {
interface BeanClass
interface BeanCollection
val allOrdered = listOf(
// 1. default group placed first to validate bean by annotations
// that are defined without groups at all, such as simple
// @Positive or @NotEmpty on some plain property
javax.validation.groups.Default::class,
// 2. then the bean class level validators must be invoked (only if
// all the properties of this bean validated by Default group are valid)
BeanClass::class,
// 3. after all, if the bean is valid by their class level validator,
// invoking validators on the outer collection property level
BeanCollection::class,
)
}
class Bean(
@field:ValidList(groups = [ValidationGroups.BeanCollection::class]) // 3. validated last
val list: List<BaseType>
)
interface BaseType
@ValidA(groups = [ValidationGroups.BeanClass::class]) // 2. validated second
class A : BaseType {
@field:Positive // 1. validated first
val id: Int
}
@ValidB(groups = [ValidationGroups.BeanClass::class]) // 2. validated second
class B : BaseType {
@field:NotEmpty // 1. validated first
val name: String
}
And the Hibernate Validator invocation:
for (group in ValidationGroups.allOrdered) {
val violations = validator.validate(bean, group.java)
if (violations.isNotEmpty()) {
return violations
}
}