I have a Spring Data REST project with an entity type with conditional validation based on a property of the entity. I want to enable certain validations using validation groups when that property is set to a specific value.
As a concrete example, take the following entity class:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
@Entity
public class Animal {
public enum Type { FLYING, OTHER }
/**
* Validation group.
*/
public interface Flying {}
@Id
@GeneratedValue
private Integer id;
private Type type;
@NotNull(groups = Flying.class)
private Integer airSpeedVelocity;
@NotNull
private Integer weight;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public Type getType() { return type; }
public void setType(Type type) { this.type = type; }
public Integer getAirSpeedVelocity() { return airSpeedVelocity; }
public void setAirSpeedVelocity(Integer airSpeedVelocity) { this.airSpeedVelocity = airSpeedVelocity; }
public Integer getWeight() { return weight; }
public void setWeight(Integer weight) { this.weight = weight;}
}
When saving an Animal
with type FLYING
, I want to validate that airSpeedVelocity
is non-null. When saving any other animal, I don't want this validation.
Currently, I have validations enable to be checked prior to save, so that a 400 Bad Request error is returned if an object is invalid:
@Bean
public ValidatingRepositoryEventListener preSaveValidator(
@Qualifier("defaultValidator") SmartValidator validator,
ObjectFactory<PersistentEntities> persistentEntitiesFactory) {
ValidatingRepositoryEventListener eventListener =
new ValidatingRepositoryEventListener(persistentEntitiesFactory);
eventListener.addValidator("beforeCreate", validator);
eventListener.addValidator("beforeSave", validator);
return eventListener;
}
}
Request:
{ "type": "FLYING" }
Current 400 error response:
{
"errors": [
{
"entity": "Animal",
"property": "weight",
"invalidValue": null,
"message": "must not be null"
}
]
}
Desired 400 error response:
{
"errors": [
{
"entity": "Animal",
"property": "airSpeedVelocity",
"invalidValue": null,
"message": "must not be null"
},
{
"entity": "Animal",
"property": "weight",
"invalidValue": null,
"message": "must not be null"
}
]
}
How can I perform this conditional validation, applying the Flying
validation group when the request entity is an Animal
where type == FLYING
?
A Hibernate Validation DefaultGroupSequenceProvider
can be used to dynamically define the default validation group based on the state of the object.
Per the reference guide:
Hibernate Validator also provides an SPI for the dynamic redefinition of default group sequences depending on the object state.
For that purpose, you need to implement the interface
DefaultGroupSequenceProvider
and register this implementation with the target class via the@GroupSequenceProvider
annotation.
In this case, a DefaultGroupSequenceProvider
can be created which uses the Flying
group (plus the standard default group) when the object's type
property is FLYING
, and the standard default group otherwise.
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
import java.util.List;
public class AnimalTypeGroupSequenceProvider
implements DefaultGroupSequenceProvider<Animal> {
@Override
public List<Class<?>> getValidationGroups(Animal object) {
if (object != null && object.getType() == Animal.Type.FLYING) {
return List.of(Animal.Flying.class, Animal.class);
} else {
return List.of(Animal.class);
}
}
}
import org.hibernate.validator.group.GroupSequenceProvider;
import javax.persistence.Entity;
@GroupSequenceProvider(AnimalTypeGroupSequenceProvider.class)
@Entity
public class Animal {
// ...
}