nrich-validation
is a module intended to add additional jakarta.validation-api
constraints that have proved to be useful on multiple occasions.
It contains a list of constraints and corresponding validators. Registration of validators is tied to hibernate-validator
implementation.
It also contains a list of messages for standard and additional constraints in english and croatian (nrich-validation-messages
).
If automatic registration of messages and validators (supported only for hibernate-validator) is required then following configuration is required
(this can also be registered through application.properties by manually including validationMessages
when using Spring Boot):
@Configuration(proxyBeanMethods = false)
public class ApplicationConfiguration {
private static final String VALIDATION_MESSAGES_NAME = "nrich-validation-messages";
@Bean
public ValidationMessageSourceRegistrar validationMessageSourceRegistrar(MessageSource messageSource) {
return new ValidationMessageSourceRegistrar(messageSource);
}
@RequiredArgsConstructor
public static class ValidationMessageSourceRegistrar implements InitializingBean {
private MessageSource messageSource;
@Override
public void afterPropertiesSet() {
if (messageSource instanceof AbstractResourceBasedMessageSource) {
((AbstractResourceBasedMessageSource) messageSource).addBasenames(VALIDATION_MESSAGES_NAME);
}
}
}
@Bean
Validator validator(ConstraintMappingRegistrar constraintValidatorRegistrar) {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
registerConstraintMappingContributors(localValidatorFactoryBean, constraintValidatorRegistrar);
return localValidatorFactoryBean;
}
@Bean
ConstraintMappingRegistrar constraintValidatorRegistrar() {
return new DefaultConstraintMappingRegistrar(List.of("net.croz.nrich.validation.constraint.validator"));
}
private void registerConstraintMappingContributors(LocalValidatorFactoryBean validator, ConstraintMappingRegistrar constraintValidatorRegistrar) {
validator.setConfigurationInitializer(constraintValidatorRegistrar::registerConstraints);
}
}
If validators don't need to be registered automatically then standard validation.xml
file in META-INF
directory should be provided. And inside ConstraintMappingContributor
implementation net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingContributor
should be registered. In that case ConstraintMappingRegistrar
bean
and registerConstraintMappingContributors
method are not needed.
The module provides following constraints:
Validates that a property is in a list of String values i.e:
@Setter
@Getter
public class ExampleRequest {
@InList(value = { "first", "second" })
private String value;
}
will validate that value is either equal to first
or equal to second
.
Validates that value doesn't exceed specified number of bytes in specified encoding (default UTF-8) i.e:
@Setter
@Getter
public class ExampleRequest {
@MaxSizeInBytes(value = 5)
private String value;
}
will validate that value contains less than or equal to 5 bytes in UTF-8
encoding.
Validates that a property is not null when a condition is satisfied i.e:
@Setter
@Getter
@NotNullWhen(property = "firstName", condition = CreatePersonRequest.Condition.class)
public class CreatePersonRequest {
private String firstName;
private String lastName;
public static class Condition implements Predicate<CreatePersonRequest> {
@Override
public boolean test(CreatePersonRequest request) {
return request.getLastName() != null;
}
}
}
will validate that firstName is not null when lastName is not null (effectively requiring both properties to be null or both properties to be empty).
If Groovy is used, condition can be written as closure.
@NotNullWhen(property = "firstName", condition = { request -> request.lastName != null })
class CreatePersonRequest {
String firstName
String lastName
}
Validates that a property is null when a condition is satisfied i.e:
@Setter
@Getter
@NullWHen(property = "companyName", condition = CreateBusinessEntityRequest.Condition.class)
public class CreateBusinessEntityRequest {
private String companyName;
private String fistName;
private String lastName;
public static class Condition implements Predicate<CreateBusinessEntityRequest> {
@Override
public boolean test(CreateBusinessEntityRequest request) {
return request.getFirstName() != null || request.getLastName() != null;
}
}
}
will validate that either companyName is filled or firstName or lastName are filled.
If Groovy is used, condition can be written as closure.
@NullWHen(property = "companyName", condition = { request -> request.fistName != null || request.lastName != null })
class CreateBusinessEntityRequest {
String companyName
String fistName
String lastName
}
Validates that file (either Spring's MultipartFile
or FilePart
) is in provided content type list, has extension that is in provided extension list and/or satisfies regex. All properties
are optional so defining a constraint without any will always pass validation.
@Setter
@Getter
public class ExampleRequest {
@ValidFile(allowedContentTypeList = "text/plain", allowedExtensionList = "txt", allowedFileNameRegex = "(?U)[\\w-.]+")
private MultipartFile file;
}
will validate that file has content type of text/plain
, extension txt
and matches regex: (?U)[\\w-.]+
Does the same thing as ValidFile
constraint but resolves allowedContentTypeList, allowedExtensionList and allowedFileNameRegex from Spring's Environment
bean allowing those properties to be
defined in property files.
@Setter
@Getter
public class ExampleRequest {
@ValidFileResolvable(
allowedContentTypeListPropertyName = "my.custom.validation.file.allowed-content-type-list",
allowedExtensionListPropertyName = "my.custom.validation.file.allowed-extension-list",
allowedFileNameRegexPropertyName = "my.custom.validation.file.allowed-file-name-regex"
)
private MultipartFile file;
}
will resolve allowedContentTypeList from my.custom.validation.file.allowed-content-type-list
, allowedExtensionList from my.custom.validation.file.allowed-extension-list
and allowedFileNameRegex from my.custom.validation.file.allowed-file-name-regex
properties and perform validation while skipping resolved empty or null property values.
Validates that a property is a valid OIB (personal identification number in Croatia).
@Setter
@Getter
public class ExampleRequest {
@ValidOib
private String value;
}
Validates that "from" property (resolved from validated class through fromPropertyName) is less than (or equal to if inclusive flag has been set) "to" property (resolved from validated class through toPropertyName) i.e:
@Setter
@Getter
@ValidRange(fromPropertyName = "dateFrom", toPropertyName = "dateTo")
public class ValidRangeValidatorDifferentTypeTestRequest {
private Instant dateFrom;
private Instant dateTo;
}
validates that dateFrom is less than dateTo.
Is useful when it is required that all properties from any group of properties are not null
@Setter
@Getter
@ValidSearchProperties(propertyGroup = { @ValidSearchProperties.PropertyGroup({ "firstName", "lastName" }), @ValidSearchProperties.PropertyGroup("id") })
public class SearchPersonRequest {
private String firstName;
private String lastName;
private Long id;
}
Above request will require that either both firstName and lastName are not null or that id is not null.
Validates property against a provided SpEL Expression
@Setter
@Getter
public class ExampleRequest {
@SpelExpression("@spelValidationTestService.validateUuid(#this)")
private String uuid;
}
@Setter
@Getter
@SpelExpression("@spelValidationTestService.validateUuid(uuid)")
public class ExampleTypeRequest {
private String uuid;
}
In an example above the property uuid is required to be a valid UUID format. The format check is achieved by calling validateUuid
method of spelValidationTestService
bean.
Validates that the annotated element is after specified minimum date
Supported types are:
- java.util.Date
- java.util.Calendar
- java.time.Instant
- java.time.LocalDate
- java.time.LocalDateTime
- java.time.LocalTime
- java.time.MonthDay
- java.time.OffsetDateTime
- java.time.OffsetTime
- java.time.Year
- java.time.YearMonth
- java.time.ZonedDateTime
@Setter
@Getter
public class ExampleRequest {
@MinDate("2023-01-01")
private LocalDate date;
}
Above request will require that the date property is after 01.01.2023
Validates that the annotated element is before end of the day
Supported types are:
- java.util.Date
- java.util.Calendar
- java.time.Instant
- java.time.LocalDate
- java.time.LocalDateTime
- java.time.LocalTime
- java.time.MonthDay
- java.time.OffsetDateTime
- java.time.OffsetTime
- java.time.Year
- java.time.YearMonth
- java.time.ZonedDateTime
@Setter
@Getter
public class ExampleRequest {
@LastTimestampInDay
private LocalDate date;
}
Above request will require that the date property is before the end of the day
Module also adds support for disabling inherited constraints with the following annotation:
Assuming the following parent class:
@Setter
@Getter
public class ExampleParentRequest {
@Size(max = 200)
@NotBlank
private String name;
@NotNull
private Integer age;
}
Constraints on name and age can be disabled by either using type annotation (if property name or constraint is not found it will just be ignored):
@DisableConstraints(value = { NotBlank.class, Size.class }, propertyName = "name")
@DisableConstraints(value = NotNull.class, propertyName = "age")
@Setter
@Getter
public class ExampleTypeRequest extends ExampleParentRequest {
}
or by using method/property annotation:
public class ExampleMethodRequest extends ExampleParentRequest {
@DisableConstraints({ NotBlank.class, Size.class })
@Override
public String getName() {
return super.getName();
}
@DisableConstraints(NotNull.class)
@Override
public Integer getAge() {
return super.getAge();
}
}