I have now tried several things, but I just do not get anywhere. I get this error message in my Spring Boot 3.0.4:
HV000030: No validator could be found for constraint 'jakarta.validation.constraints.Size' validating type 'com.example.entity.dataType.GenericString'. Check configuration for 'iso3'
I tried to register the validators with this How to register a custom constraint validator but it doesn't work.
I also tried this https://www.baeldung.com/bean-validation-container-elements. Unfortunately also without success.
I have a simple class with a property value. Here I want to be able to specify my own validators and also already common validators combined. Like e.g. also here: Combining custom constraint validator annotation
But it does not work, what am I doing wrong?
Some Source Code:
Entity Class:
public class Test {
@Id
@Column(name = "id",
nullable = false)
private long id;
@Randomizer(GenericStringValidator.class)
@Convert(converter = GenericStringConverter.class)
@JsonSerialize(using = GenericStringSerializer.class)
@Column(name = "iso3",
nullable = false,
length = 3)
@Size(min = 3,
max = 3)
private GenericString iso3;
}
Value Extractor:
@UnwrapByDefault
public class GenericStringValueExtractor implements ValueExtractor<@ExtractedValue(type = String.class) GenericString> {
@Override
public void extractValues(GenericString originalValue, ValueExtractor.ValueReceiver receiver) {
receiver.value(null, originalValue.getValue());
}
}
Copy of @Size Validator Class:
@Component
public class GenericStringSizeConstraintValidator implements ConstraintValidator<Size, GenericString> {
private int min;
private int max;
@Override
public void initialize(Size parameters) {
this.min = parameters.min();
this.max = parameters.max();
try {
this.validateParameters();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean check(String value) {
if (value == null) {
return true;
} else {
int length = value.length();
return length >= this.min && length <= this.max;
}
}
public boolean isValid(GenericString dataType, ConstraintValidatorContext constraintValidatorContext) {
if (dataType == null) {
return true;
} else {
return check(((FrameworkDataType<String>) dataType).getValue());
}
}
private void validateParameters() throws Exception {
if (this.min < 0) {
throw new Exception("Min Value cannot be nagative");
} else if (this.max < 0) {
throw new Exception("Max Value cannot be nagative");
} else if (this.max < this.min) {
throw new Exception("Length Value cannot be nagative");
}
}
}
LocalValidatorFactoryBean:
@Component
public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
@Autowired
private GenericStringSizeConstraintValidator genericStringSizeConstraintValidator;
@Override
protected void postProcessConfiguration(Configuration<?> configuration) {
super.postProcessConfiguration(configuration);
configuration.addValueExtractor(new GenericStringSizeConstraintValidator());
configuration.addValueExtractor(new GenericNameSizeConstraintValidator());
HibernateValidatorConfiguration configurationH = Validation.byProvider(HibernateValidator.class)
.configure();
ConstraintMapping constraintMapping = configurationH.createConstraintMapping();
constraintMapping.constraintDefinition(Size.class)
.includeExistingValidators(true)
.validatedBy(GenericStringSizeConstraintValidator.class);
configurationH.addMapping(constraintMapping);
}
}
EDIT 1:
I found this article that describes my problem:
https://in.relation.to/2017/03/02/adding-custom-constraint-definitions-via-the-java-service-loader/#use-standard-constraints-for-non-standard-classes but the creation of the file META-INF/services/javax.validation.ConstraintValidator
not work for Spring Boot 3
From what it seems, the source of the issue is that you are adding your constraint mappings to a configuration that is never used. In your code here:
ConstraintMapping constraintMapping = configurationH.createConstraintMapping();
constraintMapping.constraintDefinition(Size.class)
.includeExistingValidators(true)
.validatedBy(GenericStringSizeConstraintValidator.class);
configurationH.addMapping(constraintMapping);
You are creating a new configuration, but neither you nor the framework ever builds a validator factory from it.
Also, from what it looks - you don't really need the value extractors if what you only try to do is to apply @Size
to your custom type.
Now as to what you should try to do, please see the comments in the code:
@Component
public static class ValidatorFactoryBean extends LocalValidatorFactoryBean {
// No need to autowire the validator since your validator does not depend on any other beans/resources
// @Autowired
// private GenericStringSizeConstraintValidator genericStringSizeConstraintValidator;
@Override
protected void postProcessConfiguration(javax.validation.Configuration<?> configuration) {
// No need to call for super postprocess as that method does nothing
// super.postProcessConfiguration(configuration);
// No need to register value extractors as from waht it looks you don't really need them
// configuration.addValueExtractor(new GenericStringSizeConstraintValidator());
// configuration.addValueExtractor(new GenericNameSizeConstraintValidator());
// Check if you can cast a more generic configuration to a Hibernate validator specific one:
if (configuration instanceof HibernateValidatorConfiguration ) {
// Cast the configuration:
HibernateValidatorConfiguration configurationHibernate = (HibernateValidatorConfiguration) configuration;
// Create a constraint mapping using the casted configuration
ConstraintMapping constraintMapping = configurationHibernate.createConstraintMapping();
// configure your validator for the Size and a custom type:
constraintMapping.constraintDefinition( Size.class)
.includeExistingValidators(true)
.validatedBy(GenericStringSizeConstraintValidator.class);
// add your mapping to the config:
configurationHibernate.addMapping(constraintMapping);
}
}
}
As to why you probably don't need a value extractors. These were designed with the idea to apply validation to the elements that are inside of a container. In your code example a FrameworkDataType<T>
could've served as a container. If you'd wanted to write something along:
public class SomeClass {
private FrameworkDataType<@Size(max = 100) String> myContainer;
// ...
}
then you could register a value extractor for FrameworkDataType
and Hiberante Validator would take care of extracting the value from FrameworkDataType
and applying Size
validation to it.
Alternatively, if you are OK with using XML, you could register your constraint validators using XML:
have a META-INF/validation.xml
with something along the lines:
<validation-config xmlns="https://jakarta.ee/xml/ns/validation/configuration"
xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/configuration https://jakarta.ee/xml/ns/validation/validation-configuration-3.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="3.0">
<constraint-mapping>META-INF/validation/constraints-custom.xml</constraint-mapping>
</validation-config>
And then the META-INF/validation/constraints-custom.xml
<constraint-mappings
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/mapping https://jakarta.ee/xml/ns/validation/validation-mapping-3.0.xsd"
xmlns="https://jakarta.ee/xml/ns/validation/mapping" version="3.0">
<constraint-definition annotation="jakarta.validation.constraints.Size">
<validated-by include-existing-validators="true">
<value>fqcn.to.your.validator.class</value>
</validated-by>
</constraint-definition>
</constraint-mappings>