Java lightweight validator.
- Description
- Installation
- Field-Level Constraints
- Class-Level Constraints
- Create New Constraint
- Contributing
- License
SlimValidator is a Java library for providing object validation through annotations. It is inspired by the Java Bean Validation specification but is not a implementation at all.
For example, to validate the object of a class, we need to annotate its class or fields with constraints and then use the Validator
class to evaluate whether the object meets all the constraints:
/* Classes definition with constraint annotations */
class Person {
@Required
Integer id;
@Required
@Size(max = 50)
String fullName;
@Required
Gender gender;
@Range(min = 2000)
Double income;
@Size(min = 3, max = 5)
String[] hobbies;
@Valid
Address address;
@ObjectType(baseClass = {String.class})
@ObjectType(schema = Schema.COLL, baseClass = {String.class}, maxSize = 3)
Object reference;
@Extension({"jpg", "png", "bmp"})
Path photograph;
@Valid
User user;
// Constructors , getters, setters, etc.
}
class Address {
@Required
@Size(max = 50)
String streetNumberName;
@Size(max = 4)
String apartment;
@Required
String city;
// Constructors , getters, setters, etc.
}
@RequiredIfNull(fields = {"userName"}, dependsOn = "email")
@FieldMatch(first = "password", second = "confirmPassword")
class User {
String username;
String email;
@Required
String password;
@Required
String confirmPassword;
// Constructors , getters, setters, etc.
}
enum Gender {
MALE,
FEMALE,
OTHER
}
/* Instantiate objects of those classes */
var address = new Address();
address.setStreetNumberName("1765 Paramount Avenue");
address.setApartment("123-A");
var user = new User();
user.setPassword("q1w2e3");
user.setConfirmPassword("q1w2e3");
var person = new Person();
person.setFullName("Martin Jefferson");
person.setGender(Gender.MALE);
person.setIncome(1850.5);
person.setHobbies(new String[] {"dancing", "running"});
person.setAddress(address);
person.setReference(List.of(10, 20));
person.setPhotograph(Paths.get("src/test/resources/sample.txt"));
person.setUser(user);
/* Validate objects */
var validator = new Validator();
var violations = validator.validate(person);
if (violations.size() > 0) {
violations.forEach(v -> System.out.println(v.getName() + " " + v.getMessage()));
}
As a result of the validation process, you will see the following messages in console, because the object does not meet several constraints:
id must have a value.
income must be at least 2000.
hobbies size must be at least 3 at most 5.
address.apartment size must be at most 4.
address.city must have a value.
reference type must be one of String or Collection<String> and size at most 3.
photograph extension must be one of [jpg, png, bmp].
in user [username] must have a value when email is null.
You can install this library by adding the following dependency to your Maven project:
<dependency>
<groupId>io.github.sashirestela</groupId>
<artifactId>slimvalidator</artifactId>
<version>[latest version]</version>
</dependency>
Or alternatively using Gradle:
dependencies {
implementation 'io.github.sashirestela:slimvalidator:[latest version]'
}
NOTE: Requires Java 11 or greater.
- Description: Checks that a value is not null. In case the value is a group (Collection, Map, Array) checks that it is not empty.
- Applies to: Fields of any type.
- Parameters:
- (none).
- Error messages:
- If the value is null or an empty group:
- must have a value.
- If the value is null or an empty group:
- Examples:
@Required private Long id; @Required private List<Adress> addresses;
- Description: Checks that a numeric value is within a closed range.
- Applies to: Fields of any numeric type.
- Exceptions:
- When neither min nor max are set.
- When min is not less than max.
- When the value is of an unexpected type, unless isVariableType is true.
- Parameters:
- min: The lowest value of the range. By default is -Double.MAX_VALUE.
- max: The greatest value of the range. By default is Double.MAX_VALUE.
- isVariableType: Whether the field type is variable. The default value is false.
- Error messages:
- If min was set and the value is lower:
- must be at least {min}.
- If max was set and the value is greater:
- must be at most {max}.
- If min and max were set and the value is out of range:
- must be at least {min} at most {max}.
- If min was set and the value is lower:
- Example:
@Range(min = 0.0, max = 100.0) private Double grade;
- Description: Checks that a text's length or a group's size is within a closed range.
- Applies to: Fields of type: String, Collection, Map, Array.
- Exceptions:
- When min is greater than max.
- When the value is of an unexpected type, unless isVariableType is true.
- Parameters:
- min: The lowest value of the length or size. By default is 0.
- max: The greatest value of the length or size. By default is Integer.MAX_VALUE.
- isVariableType: Whether the field type is variable. The default value is false.
- Error messages:
- If min was set and the length or size is lower:
- size must be at least {min}.
- If max was set and the length or size is greater:
- size must be at most {max}.
- If min and max were set and the length or size is out of range:
- size must be at least {min} at most {max}.
- If min was set and the length or size is lower:
- Example:
@Size(min = 2, max = 5) private List<Project> projects;
- Description: Checks that the file extension is one of an expected list.
- Applies to: Fields of type: java.nio.file.Path or java.io.File.
- Exceptions:
- When the underlying filename is empty.
- When the filename extension is bad formed.
- When the value is of an unexpected type, unless isVariableType is true.
- Parameters:
- value: Array of expected extensions. Mandatory.
- isVariableType: Whether the field type is variable. The default value is false.
- Error messages:
- If file extension is not any of the value array:
- extension must be one of {value}.
- If file extension is not any of the value array:
- Example:
@Extension({"doc", "xls", "txt"}) private Path evidenceFile;
- Description: Checks that the type of an object matches a specific schema pattern. Supports direct objects, collections, nested collections, maps, and maps with collection values.
- Applies to: Fields of the Object type, including various collection and map structures.
- Parameters:
- schema: The schema pattern to validate against. Options: DIRECT, COLL, COLL_COLL, MAP, MAP_COLL. By default is DIRECT.
- baseClass: Array of candidate base classes for the field. Mandatory.
- keyClass: The class type for map keys when using MAP or MAP_COLL schema. By default is void.class.
- maxSize: The maximum size of the outer collection or map. By default is Integer.MAX_VALUE.
- maxInnerSize: The maximum size of inner collections. By default is Integer.MAX_VALUE.
- maxChecks: The maximum number of items to check for performance optimization. By default is 20.
- allowNull: Whether to allow null values in collections or maps. By default is true.
- allowInnerNull: Whether to allow null values in inner collections. By default is true.
- Schema Types:
- DIRECT: Validates direct object types (baseClass)
- COLL: Validates Collection<baseClass>
- COLL_COLL: Validates Collection<Collection<baseClass>>
- MAP: Validates Map<keyClass, baseClass>
- MAP_COLL: Validates Map<keyClass, Collection<baseClass>>
- Error messages:
- Dynamically generated based on schema type and configuration parameters
- Example:
// directReference could be: String or Integer @ObjectType(baseClass = {String.class, Integer.class}) private Object directReference; // mapReference could be: Map<String, Double> or Map<String, Integer> @ObjectType(schema = Schema.MAP, keyClass = String.class, baseClass = {Double.class, Integer.class}) private Object mapReference; // multiSchemaReference could be: String or List<String> or List<List<String>> @ObjectType(baseClass = {String.class}) @ObjectType(schema = Schema.COLL, baseClass = {String.class}, maxSize = 2) @ObjectType(schema = Schema.COLL_COLL, baseClass = {String.class}, maxSize = 2) private Object multiSchemaReference;
- Description: Flag to do nested validation for fields without any constraint.
- Applies to: Fields of custom classes that do not have any constraint but it requires to validate their nested fields. Any field-level constraint enable nested validation automatically.
- Parameters:
- (none).
- Error messages:
- (none).
- Example:
@Valid private Address mainAddress;
- Description: Checks that all fields in a list are not null when the dependsOn field is null.
- Applies to: Fields of any type.
- Parameters:
- fields: Array of field names to evaluate. Mandatory.
- dependsOn: Name of the reference field. Mandatory.
- Error messages:
- If any of the fields is null when the dependsOn field is null:
- {fields} must have a value when {dependsOn} is null.
- If any of the fields is null when the dependsOn field is null:
- Examples:
@RequiredIfNull(fields = {"firstName", "lastName"}, dependsOn = "fullName") class User { private String firstName; private String lastName; private String fullName; }
- Description: Checks that two fields match.
- Applies to: Fields of any type.
- Parameters:
- first: First field name. Mandatory.
- second: Second field name. Mandatory.
- Error messages:
- If both fields do not match:
- {first} and {second} must match.
- If both fields do not match:
- Examples:
@FieldMatch(first = "password", second = "confirmPassword") class User { private String password; private String confirmPassword; }
For creating a new constraint you need to create both a new constraint annotation and a new validator class:
Create a new annotation YourNewConstraint
with the following template:
@Documented
@Constraint(validatedBy = YourNewValidator.class)
@Target({ ElementType.FIELD }) // For filed-level constraint OR
@Target({ ElementType.TYPE }) // For class-level constraint
@Retention(RetentionPolicy.RUNTIME)
public @interface YourNewConstraint {
// Add any annotation methods needed by your new constraint.
}
- Use the
@Constraint
annotation to linkYourNewConstraint
annotation toYourNewValidator
class. - Add any other annotation methods needed by your new constraint.
Create a new class YourNewValidator
with the following template:
public class YourNewValidator implements ConstraintValidator<YourNewConstraint, ClassOfObjectsToValidate> {
private Type1 annotMethod1;
private Type2 annotMethod2;
...
@Override
public void initialize(YourNewConstraint annotation) {
annotMethod1 = annotation.annotMethod1();
annotMethod2 = annotation.annotMethod2();
...
}
@Override
public String getMessage() {
// This the message to output when object violates the constraint.
// Prepare it using the annotation methods values gathered in initialize().
// Example:
// return "Custom message with " + annotMethod1 + " and " + annotMethod2;
}
@Override
public boolean isValid(Object value) {
// This condition applies for field-level constraints only
if (value == null) {
return true;
}
// Add your validation logic here
return validationResult;
}
}
- Implement the
ConstraintValidator<A, T>
interface, where A represents YourNewConstraint and T represents the class of the objects to validate, in this case, you can useObject
if your validations applies to more than one class. - Create as field members as annotation methods you have in YourNewConstraint.
- Override the
initialize()
method to capture the annotation method values in your field members. - Override the
getMessage()
method to build the error message using the annotation method values. - Override the
isValid()
method to do the validation logic. For field-level constraints only: your first validation step must return true if the object to validate is null, because we have the annotation@Required
to validate that condition, we don't want to evaluate that nullity here.
Please read our Contributing guide to learn and understand how to contribute to this project.
This library is licensed under the MIT License. See the LICENSE file for more information.