ValidX v1.2.0 Changelog
Release Date: 2026-08-25
This document records the changes from v1.1.0 to v1.2.0.
Change Overview
- ⚠️ Breaking Changes
-
isStartsWith()andisEndsWith()chain API parameter changed fromString[]toString - Chain API method renames:
isAlphaNum()→isAlphaNumber(),isMacAddress()→isMac()(aligned 1:1 with annotation names)
-
- ✨ New Features
- New
@StartsWithAnymultiple prefix validation annotation - New
@EndsWithAnymultiple suffix validation annotation - New
@NationalityCodenationality code validation annotation (ISO 3166-1)
- New
- 🔧 Enhancements
-
@FileSizeannotation now supports MIME type validation withallowedTypesparameter -
@StartsWith,@EndsWithnow supportignoreCaseparameter for case-insensitive matching -
@Urlannotation now supportsprotocolsparameter for protocol whitelist configuration (default: http / https / ftp)
-
- 🎯 Code Refactoring
- Simplified validator initialization code across multiple validators
- Removed verbose anonymous annotation instance creation
- Improved code maintainability and readability
Breaking Changes ⚠️
Chain API Parameter Change: isStartsWith() and isEndsWith()
The isStartsWith() and isEndsWith() methods in the chain API have been refactored to accept a single String parameter instead of String[] to better align with their single-value validation purpose.
What Changed:
Before (v1.1.0):
ValidX validator = ValidX.init();
// Old API - accepted String[] for single prefix/suffix
validator.isStartsWith("http://example.com", new String[]{"http://"});
validator.isEndsWith("photo.jpg", new String[]{".jpg"});
After (v1.2.0):
ValidX validator = ValidX.init();
// New API - accepts String for single prefix/suffix
validator.isStartsWith("http://example.com", "http://");
validator.isEndsWith("photo.jpg", ".jpg");
// For multiple prefixes/suffixes, use the new *Any methods
validator.isStartsWithAny("http://example.com", new String[]{"http://", "https://"});
validator.isEndsWithAny("photo.jpg", new String[]{".jpg", ".jpeg", ".png"});
Migration Guide:
- Single Prefix/Suffix Validation:
// v1.1.0 code
validator.isStartsWith(url, new String[]{"http://"});
validator.isEndsWith(file, new String[]{".jpg"});
// v1.2.0 migration - remove array wrapper
validator.isStartsWith(url, "http://");
validator.isEndsWith(file, ".jpg");
- Multiple Prefix/Suffix Validation:
// v1.1.0 code
validator.isStartsWith(url, new String[]{"http://", "https://"});
validator.isEndsWith(file, new String[]{".jpg", ".jpeg", ".png"});
// v1.2.0 migration - use new *Any methods
validator.isStartsWithAny(url, new String[]{"http://", "https://"});
validator.isEndsWithAny(file, new String[]{".jpg", ".jpeg", ".png"});
Rationale:
-
Semantic Clarity:
isStartsWith()for single value,isStartsWithAny()for multiple values -
API Consistency: Aligns with the annotation behavior (
@StartsWithvs@StartsWithAny) - Better Developer Experience: More intuitive and less verbose for single-value cases
- Type Safety: Eliminates confusion between single and multiple value validation
Impact:
- Affects only chain API users who use
isStartsWith()orisEndsWith()methods - Annotation-based validation (
@StartsWith,@EndsWith) remains unchanged - Simple migration: remove array wrapper for single values, or use
*Anymethods for multiple values
Chain API Method Renames: isAlphaNum() → isAlphaNumber(), isMacAddress() → isMac()
The isAlphaNum() and isMacAddress() methods in the chain API have been renamed to align 1:1 with their corresponding annotations (@AlphaNumber ↔ isAlphaNumber, @Mac ↔ isMac).
What Changed:
Before (v1.1.0):
ValidX validator = ValidX.init();
validator.isAlphaNum("abc123"); // old name
validator.isMacAddress("00:1A:2B:3C:4D:5E"); // old name
After (v1.2.0):
ValidX validator = ValidX.init();
validator.isAlphaNumber("abc123"); // aligned with @AlphaNumber
validator.isMac("00:1A:2B:3C:4D:5E"); // aligned with @Mac
Migration Guide:
// v1.1.0 code
validator.isAlphaNum(value);
validator.isMacAddress(value);
// v1.2.0 migration - simply replace method names; parameters and behavior unchanged
validator.isAlphaNumber(value);
validator.isMac(value);
Rationale:
- Annotation/Chain 1:1 Alignment: Chain method names now match annotation names, eliminating confusion between matching annotations and chain methods
- API Consistency: Whether using annotations or the chain API, rule names correspond exactly, reducing lookup friction
Impact:
- Affects only chain API users who use
isAlphaNum()orisMacAddress()methods - Annotation-based validation (
@AlphaNumber,@Mac) remains unchanged - Simple migration: replace the method name directly; parameters and behavior unchanged
New Features ✨
1. @StartsWithAny Multiple Prefix Validation Annotation
Added dedicated multiple prefix validation annotation to verify that strings start with any one of the specified prefixes.
Features:
- Validates if a string starts with any of the specified prefixes
- Supports validation of multiple prefixes (e.g., URL protocol validation, title validation)
- Supports
ignoreCaseparameter for case-insensitive matching - Null and empty strings pass validation by default
- Full internationalization support (9 languages)
- Complements the
@StartsWithannotation for flexible prefix validation
Annotation Examples:
public class RequestDTO {
// Example 1: URL protocol validation
@StartsWithAny({"http://", "https://"})
private String url;
// Example 2: Title validation
@StartsWithAny({"Mr.", "Mrs.", "Ms.", "Dr."})
private String title;
// Example 3: Chinese surname validation
@StartsWithAny({"张", "王", "李", "赵"})
private String chineseName;
// Example 4: File path validation
@StartsWithAny({"/home/", "/usr/", "/opt/"})
private String filePath;
// Example 5: Case-insensitive validation
@StartsWithAny(value = {"http://", "https://"}, ignoreCase = true)
private String urlCaseInsensitive;
}
Chain API Examples:
ValidX validator = ValidX.init();
// Basic usage
validator.field("URL").isStartsWithAny("http://example.com", new String[]{"http://", "https://"});
// Case-insensitive
validator.field("URL").isStartsWithAny("HTTP://example.com", new String[]{"http://", "https://"}, true);
// Multiple prefix options
validator.field("Name").isStartsWithAny("Mr. Smith", new String[]{"Mr.", "Mrs.", "Ms.", "Dr."});
// Chinese text validation
validator.field("Name").isStartsWithAny("张三", new String[]{"张", "王", "李", "赵"});
// Check validation result
if (!validator.passed()) {
System.out.println(validator.getErrors());
}
Real-World Use Cases:
// Use Case 1: URL security validation
@RestController
public class LinkController {
@PostMapping("/links")
public Result addLink(@Valid @RequestBody LinkDTO dto) {
return linkService.add(dto);
}
}
public class LinkDTO {
@NotBlank(message = "URL is required")
@StartsWithAny({"http://", "https://"})
private String url;
}
// Use Case 2: Form validation with titles
ValidX validator = ValidX.init()
.config(ValidXConfig.GLOBAL_NOT_NULL)
.field("Title").isStartsWithAny(fullName, new String[]{"Mr.", "Mrs.", "Ms.", "Dr."});
// Use Case 3: File path security validation
public class FileAccessDTO {
@StartsWithAny({"/home/", "/tmp/", "/var/log/"})
private String allowedPath;
}
Notes:
- Case-sensitive by default (e.g., "HTTP://" will not match "http://"), use
ignoreCase = truefor case-insensitive matching - Null and empty strings pass validation (use with
@NotNullor@NotEmptyif required) - Empty prefix array will cause validation to fail
- Empty string prefix matches all strings (any string starts with empty string)
- Common use cases: URL validation, file path validation, title/prefix validation, Chinese surname validation
2. @EndsWithAny Multiple Suffix Validation Annotation
Added dedicated multiple suffix validation annotation to verify that strings end with any one of the specified suffixes.
Features:
- Validates if a string ends with any of the specified suffixes
- Supports validation of multiple suffixes (e.g., file extension validation, name suffix validation)
- Supports
ignoreCaseparameter for case-insensitive matching - Null and empty strings pass validation by default
- Full internationalization support (9 languages)
- Complements the
@EndsWithannotation for flexible suffix validation
Annotation Examples:
public class FileDTO {
// Example 1: Image file validation
@EndsWithAny({".jpg", ".jpeg", ".png", ".gif"})
private String imageFile;
// Example 2: Document file validation
@EndsWithAny({".txt", ".doc", ".docx", ".pdf"})
private String documentFile;
// Example 3: Chinese name suffix validation
@EndsWithAny({"先生", "女士", "小姐"})
private String chineseName;
// Example 4: Compressed file validation
@EndsWithAny({".zip", ".rar", ".7z", ".tar.gz"})
private String archiveFile;
// Example 5: Case-insensitive validation
@EndsWithAny(value = {".jpg", ".jpeg", ".png"}, ignoreCase = true)
private String imageCaseInsensitive;
}
Chain API Examples:
ValidX validator = ValidX.init();
// Basic usage
validator.field("File").isEndsWithAny("photo.jpg", new String[]{".jpg", ".jpeg", ".png", ".gif"});
// Case-insensitive
validator.field("File").isEndsWithAny("photo.JPG", new String[]{".jpg", ".jpeg", ".png"}, true);
// Multiple suffix options
validator.field("Document").isEndsWithAny("report.pdf", new String[]{".txt", ".doc", ".docx", ".pdf"});
// Chinese text validation
validator.field("Name").isEndsWithAny("张先生", new String[]{"先生", "女士", "小姐"});
// Check validation result
if (!validator.passed()) {
System.out.println(validator.getErrors());
}
Real-World Use Cases:
// Use Case 1: File upload validation
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(@Valid @RequestBody UploadDTO dto) {
return uploadService.handle(dto);
}
}
public class UploadDTO {
@NotBlank(message = "Filename is required")
@EndsWithAny({".jpg", ".jpeg", ".png", ".gif"})
private String fileName;
}
// Use Case 2: Document type validation
ValidX validator = ValidX.init()
.config(ValidXConfig.GLOBAL_NOT_NULL)
.field("Document").isEndsWithAny(fileName, new String[]{".txt", ".doc", ".docx", ".pdf"});
// Use Case 3: Chinese name suffix validation
public class PersonDTO {
@EndsWithAny({"先生", "女士", "小姐", "教授", "博士"})
private String fullName;
}
Notes:
- Case-sensitive by default (e.g., ".JPG" will not match ".jpg"), use
ignoreCase = truefor case-insensitive matching - Null and empty strings pass validation (use with
@NotNullor@NotEmptyif required) - Empty suffix array will cause validation to fail
- Empty string suffix matches all strings (any string ends with empty string)
- Common use cases: file extension validation, archive format validation, name suffix validation, Chinese honorific validation
3. @NationalityCode Nationality Code Validation Annotation
Added a nationality code validation annotation to verify that a string is a valid ISO 3166-1 country/region code (two-letter, three-letter, or three-digit).
Features:
- Validates ISO 3166-1 country/region codes in three encoding forms: two-letter (alpha-2), three-letter (alpha-3), and three-digit (numeric)
- Built-in
NationalityCodeTypeenum; theformatsparameter specifies allowed encoding forms, defaulting to all three - Backed by the
IsoCountryenum (249 countries/regions) - Case-insensitive matching; values are uppercased before matching
- Null and empty strings pass validation by default
- Full internationalization support (9 languages)
- Typical use case: the 4th-6th digits of the Foreigner's Permanent Residence ID (Five-Star Card) are a three-digit nationality code; specify
NUMERICwhen validating them
Annotation Examples:
public class PersonDTO {
// Example 1: All three forms accepted by default
@NationalityCode
private String countryCode; // "CA", "CAN", and "124" all pass
// Example 2: Five-Star Card validation - numeric code only (braces may be omitted for a single value)
@NationalityCode(formats = NationalityCode.NationalityCodeType.NUMERIC)
private String nationalityCode; // Only numeric codes like "124" pass
// Example 3: Two-letter and three-letter only (braces required for multiple values)
@NationalityCode(formats = {NationalityCode.NationalityCodeType.ALPHA_2, NationalityCode.NationalityCodeType.ALPHA_3})
private String alphaCode; // Only "CA" and "CAN" pass
}
Chain API Examples:
ValidX validator = ValidX.init();
// All three forms accepted by default
validator.isNationalityCode("124");
// Numeric code only (for Five-Star Card 4th-6th digit validation)
validator.isNationalityCode("124", new NationalityCode.NationalityCodeType[]{NationalityCode.NationalityCodeType.NUMERIC});
// Check validation result
if (!validator.passed()) {
System.out.println(validator.getErrors());
}
Real-World Use Cases:
// Use Case 1: Foreigner's Permanent Residence ID (Five-Star Card) validation
public class ForeignerResidenceDTO {
@NationalityCode(formats = NationalityCode.NationalityCodeType.NUMERIC)
private String nationalityCode; // 4th-6th digits of the Five-Star Card number
}
// Use Case 2: Generic country/region code entry (any form)
public class CountryDTO {
@NationalityCode
private String countryCode;
}
// Use Case 3: Chain validation
ValidX validator = ValidX.init()
.config(ValidXConfig.GLOBAL_NOT_NULL)
.field("Country Code").isNationalityCode("CAN");
Notes:
-
formatsis an array type; braces may be omitted for a single value (formats = ALPHA_2is equivalent toformats = {ALPHA_2}), but are required for two or more values - Matching is case-insensitive; values are uppercased before matching
- Null and empty strings pass validation (use with
@NotNullor@NotEmptyfor required fields) - Common use cases: nationality/country code entry validation, Foreigner's Permanent Residence ID (Five-Star Card) number validation
Enhancements 🔧
1. @StartsWith, @EndsWith Support Case-Insensitive Matching
Added ignoreCase parameter to existing prefix and suffix validation annotations, enabling case-insensitive string matching.
New Feature:
- Added optional
ignoreCaseparameter, defaults tofalse(case-sensitive) - Adds new functionality to v1.0.0
@StartsWithand@EndsWithannotations - Chain API also supports
ignoreCaseparameter - Maintains backward compatibility, default behavior unchanged
Note: The new @StartsWithAny and @EndsWithAny annotations also support the ignoreCase parameter, see New Features section.
Annotation Examples:
public class RequestDTO {
// Example 1: URL protocol validation (case-insensitive)
@StartsWith(startsWith = "http://", ignoreCase = true)
private String url; // Both "HTTP://example.com" and "http://example.com" pass
// Example 2: File extension validation (case-insensitive)
@EndsWith(endsWith = ".jpg", ignoreCase = true)
private String imageFile; // Both "photo.JPG" and "photo.jpg" pass
}
Chain API Examples:
ValidX validator = ValidX.init();
// StartsWith - case-insensitive
validator.isStartsWith("HTTP://example.com", "http://", true); // passes
// EndsWith - case-insensitive
validator.isEndsWith("file.TXT", ".txt", true); // passes
// Check validation result
if (!validator.passed()) {
System.out.println(validator.getErrors());
}
Real-world Use Cases:
// Use Case 1: User-entered URL validation (users may input uppercase)
@RestController
public class LinkController {
@PostMapping("/links")
public Result addLink(@Valid @RequestBody LinkDTO dto) {
return linkService.add(dto);
}
}
public class LinkDTO {
@NotBlank(message = "URL cannot be empty")
@StartsWith(startsWith = "http://", ignoreCase = true)
private String url; // Accepts "HTTP://", "Http://", "http://", etc.
}
// Use Case 2: File extension validation (Windows users may use uppercase extensions)
public class FileDTO {
@NotBlank(message = "Filename cannot be empty")
@EndsWith(endsWith = ".txt", ignoreCase = true)
private String fileName; // Accepts ".TXT", ".Txt", ".txt", etc.
}
// Use Case 3: Chain validation for file paths
ValidX validator = ValidX.init()
.config(ValidXConfig.GLOBAL_NOT_NULL)
.field("File Path").isStartsWith(filePath, "/home/", true); // Accepts "/HOME/", "/Home/", etc.
Notes:
-
ignoreCaseparameter defaults tofalse, maintaining original case-sensitive behavior - When
ignoreCase = trueis set, usestoLowerCase()for case-insensitive comparison - Suitable for scenarios requiring tolerance for inconsistent user input casing
- Minimal performance impact (only adds one
toLowerCase()call)
2. @FileSize Supports MIME Type Validation
Enhanced the @FileSize annotation with a new allowedTypes parameter to validate file MIME types, particularly useful for MultipartFile validation in Spring applications.
New Feature:
- Added optional
allowedTypesparameter to restrict allowed MIME types - Works seamlessly with Spring's
MultipartFile - Validates both file size and MIME type in a single annotation
- Continues to support
java.io.File,java.nio.file.Path, andbyte[]types
Annotation Examples:
public class FileUploadDTO {
// Example 1: Image file with size and type validation
@FileSize(max = "5MB", allowedTypes = {"image/jpeg", "image/png", "image/gif"})
private MultipartFile avatar;
// Example 2: Document file with type restrictions
@FileSize(min = "1KB", max = "10MB", allowedTypes = {"application/pdf", "application/msword"})
private MultipartFile document;
// Example 3: Multiple image formats
@FileSize(max = "2MB", allowedTypes = {"image/jpeg", "image/jpg", "image/png", "image/webp"})
private MultipartFile photo;
}
Chain API Examples:
ValidX validator = ValidX.init();
// With MIME type validation
validator.field("Avatar").isFileSize(
avatarFile,
"0B", // min size
"5MB", // max size
new String[]{"image/jpeg", "image/png"} // allowed MIME types
);
Real-World Use Cases:
// Use Case 1: Profile avatar upload
@RestController
public class ProfileController {
@PostMapping("/avatar")
public Result uploadAvatar(@Valid @RequestBody AvatarDTO dto) {
return profileService.updateAvatar(dto);
}
}
public class AvatarDTO {
@NotNull(message = "Avatar is required")
@FileSize(max = "5MB", allowedTypes = {"image/jpeg", "image/png", "image/gif"})
private MultipartFile avatar;
}
// Use Case 2: Document upload with strict type control
public class DocumentDTO {
@FileSize(
min = "1KB",
max = "20MB",
allowedTypes = {"application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}
)
private MultipartFile document;
}
Notes:
-
allowedTypesparameter is optional; when empty, only size is validated - MIME type validation only works with file types that provide MIME information (e.g.,
MultipartFile) - For
File,Path, andbyte[]types,allowedTypesis ignored - Common MIME types:
image/jpeg,image/png,image/gif,application/pdf,text/plain, etc.
3. @Url Supports Protocol Whitelist
Added a protocols parameter to the @Url annotation, allowing configuration of the allowed URL protocol whitelist to restrict or extend accepted URL protocols.
New Feature:
- Added optional
protocolsparameter to configure the allowed protocol whitelist - Default whitelist is
{"http", "https", "ftp"}, consistent with historical behavior (backward compatible) - Chain API supports new
isUrl(value, protocols...)overload - Protocol matching is case-insensitive
- Whitelist can restrict (e.g., HTTPS only) or extend allowed protocols
Annotation Examples:
public class RequestDTO {
// Default whitelist: http / https / ftp (backward compatible)
@Url
private String url;
// HTTPS only
@Url(protocols = {"https"})
private String secureUrl;
// Web protocols only
@Url(protocols = {"http", "https"})
private String webUrl;
}
Chain API Examples:
ValidX validator = ValidX.init();
// Default whitelist (http / https / ftp)
validator.isUrl("http://example.com");
// Specified protocol whitelist (https only)
validator.isUrl("https://example.com", "https");
Notes:
- The
protocolsparameter is optional; default{"http", "https", "ftp"}maintains backward compatibility with no breaking changes - Protocol matching is case-insensitive
- Null and empty strings still pass validation (use with
@NotNullor@NotEmptyfor required fields)
Code Refactoring 🎯
Validator Initialization Simplification
Significantly simplified validator initialization code across the codebase, removing verbose anonymous annotation instance creation and improving maintainability.
Improvements:
-
Removed Verbose Anonymous Classes
- Eliminated repetitive anonymous annotation implementations
- Validators now support direct parameter initialization
- Reduced code size by approximately 500+ lines
-
Affected Validators:
-
InValidatorandNotInValidator EnumValidatorFileExtensionValidator-
FileSizeValidatorfamily (all 4 variants) PasswordValidator-
StartsWithandEndsWithValidator -
UUIDValidator,Base64Validator,AgeValidator -
JSONValidator,PhoneNumberValidator -
TimestampValidator,IpValidator -
StockCodeValidator,FinancialProductCodeValidator
-
-
Benefits:
- Cleaner Code: Removed 30+ lines per validator on average
- Better Maintainability: Easier to understand and modify
- Improved Performance: Slightly faster initialization
- Consistent Pattern: All validators now follow the same initialization approach
Before (Old Code):
public void validateIn(Object value, String[] values, List<String> errors, Locale locale) {
InValidator validator = new InValidator();
// Create mock annotation instance (30+ lines of boilerplate)
In inAnnotation = new In() {
@Override
public Class<? extends java.lang.annotation.Annotation> annotationType() {
return In.class;
}
@Override
public String[] value() {
return values != null ? values : new String[0];
}
// ... more boilerplate methods
};
validator.initialize(inAnnotation);
if (!validator.isValid(value, null)) {
errors.add(MessageManager.getMessage("io.github.vipxieliang.validx.annotation.in", locale));
}
}
After (New Code):
public void validateIn(Object value, String[] values, List<String> errors, Locale locale) {
InValidator validator = new InValidator();
validator.initialize(values);
if (!validator.isValid(value, null)) {
errors.add(MessageManager.getMessage("io.github.vipxieliang.validx.annotation.in", locale));
}
}
Impact:
- Total lines reduced: 500+ lines
- Files refactored: 20+ validator classes
- Improved code readability across the entire validation chain
- No functional changes - all existing tests pass without modification
Internationalization Support 🌍
All three new annotations support the following 9 languages:
-
Chinese (Simplified) -
ValidationMessages.propertiesandValidationMessages_zh.properties -
English -
ValidationMessages_en.properties -
Japanese -
ValidationMessages_ja.properties -
Korean -
ValidationMessages_ko.properties -
French -
ValidationMessages_fr.properties -
German -
ValidationMessages_de.properties -
Spanish -
ValidationMessages_es.properties -
Russian -
ValidationMessages_ru.properties
Error Messages:
-
@StartsWithAny: "Does not start with any of the specified strings" -
@EndsWithAny: "Does not end with any of the specified strings" -
@NationalityCode: "Invalid ISO 3166-1 country code (two-letter, three-letter, or three-digit)"
All language packs maintain consistent message format with proper Unicode encoding.
Testing Coverage 🧪
Comprehensive test coverage for all three new features:
Validator Tests (Bean Validation Framework):
-
StartsWithAnyValidatorTest: 9 test cases covering valid/invalid scenarios, null/empty values, case sensitivity, empty arrays -
EndsWithAnyValidatorTest: 9 test cases with the same comprehensive coverage
Chain Validation Tests:
-
StartsWithAnyValidationChainTest: 13 test cases for chain API usage -
EndsWithAnyValidationChainTest: 13 test cases for chain API usage
Internationalization Tests:
-
StartsWithAnyI18nTest: 8 test cases (one per language) -
EndsWithAnyI18nTest: 8 test cases (one per language)
Nationality Code Validation Tests:
-
NationalityCodeValidatorTest: 6 test cases covering valid/invalid codes, variousformatscombinations, and null/empty values -
NationalityCodeValidationChainTest: 4 test cases for chain API usage
Total: 70 new test cases, all passing ✅
Related Links 🔗
Released with ❤️ by the ValidX Team
Top comments (0)