Apache Commons Lang provides essential Java utility classes for string manipulation, object operations, array handling, date/time processing, reflection utilities, and more.
—
Apache Commons Lang provides comprehensive validation utilities through the Validate class, offering 51 static methods for argument validation, precondition checking, and defensive programming. These utilities throw clear, informative exceptions with consistent error messages.
The Validate class provides fluent, null-safe validation methods that throw IllegalArgumentException with descriptive messages:
import org.apache.commons.lang3.Validate;// Null checking methods
public static <T> T notNull(T object)
public static <T> T notNull(T object, String message, Object... values)
// Object validation
public static void validIndex(Object[] array, int index)
public static void validIndex(Object[] array, int index, String message, Object... values)
public static void validIndex(Collection<?> collection, int index)
public static void validIndex(Collection<?> collection, int index, String message, Object... values)
public static void validIndex(CharSequence chars, int index)
public static void validIndex(CharSequence chars, int index, String message, Object... values)Usage Examples:
public class UserService {
public User createUser(String name, String email, Integer age) {
// Basic null validation
Validate.notNull(name, "Name cannot be null");
Validate.notNull(email, "Email cannot be null");
Validate.notNull(age, "Age cannot be null");
// Validation with formatted message
Validate.notNull(name, "User %s field cannot be null", "name");
return new User(name, email, age);
}
public String getCharacterAt(String text, int index) {
Validate.notNull(text, "Text cannot be null");
Validate.validIndex(text, index, "Invalid index %d for text of length %d", index, text.length());
return String.valueOf(text.charAt(index));
}
public <T> T getElementAt(List<T> list, int index) {
Validate.notNull(list, "List cannot be null");
Validate.validIndex(list, index, "Index %d is out of bounds for list of size %d", index, list.size());
return list.get(index);
}
}// Collection and array validation
public static <T extends Collection<?>> T notEmpty(T collection)
public static <T extends Collection<?>> T notEmpty(T collection, String message, Object... values)
public static <T> T[] notEmpty(T[] array)
public static <T> T[] notEmpty(T[] array, String message, Object... values)
// Map validation
public static <T extends Map<?, ?>> T notEmpty(T map)
public static <T extends Map<?, ?>> T notEmpty(T map, String message, Object... values)
// String validation
public static <T extends CharSequence> T notEmpty(T chars)
public static <T extends CharSequence> T notEmpty(T chars, String message, Object... values)
public static <T extends CharSequence> T notBlank(T chars)
public static <T extends CharSequence> T notBlank(T chars, String message, Object... values)Usage Examples:
public class ValidationExamples {
public void processData(List<String> items, String[] categories, Map<String, Object> config) {
// Collection validation
Validate.notEmpty(items, "Items list cannot be empty");
Validate.notEmpty(categories, "Categories array cannot be empty");
Validate.notEmpty(config, "Configuration map cannot be empty");
// Process data...
}
public User authenticateUser(String username, String password) {
// String validation (notEmpty allows whitespace-only strings)
Validate.notEmpty(username, "Username cannot be empty");
Validate.notEmpty(password, "Password cannot be empty");
// Blank validation (rejects whitespace-only strings)
Validate.notBlank(username, "Username cannot be blank");
Validate.notBlank(password, "Password cannot be blank");
return authenticate(username.trim(), password);
}
public void saveConfiguration(Map<String, String> settings) {
Validate.notEmpty(settings, "Settings cannot be empty");
// Validate individual entries
for (Map.Entry<String, String> entry : settings.entrySet()) {
Validate.notBlank(entry.getKey(), "Setting key cannot be blank");
Validate.notBlank(entry.getValue(), "Setting value for '%s' cannot be blank", entry.getKey());
}
}
}// Inclusive range validation
public static void inclusiveBetween(double start, double end, double value)
public static void inclusiveBetween(double start, double end, double value, String message, Object... values)
public static void inclusiveBetween(long start, long end, long value)
public static void inclusiveBetween(long start, long end, long value, String message, Object... values)
public static <T> void inclusiveBetween(T start, T end, Comparable<T> value)
public static <T> void inclusiveBetween(T start, T end, Comparable<T> value, String message, Object... values)
// Exclusive range validation
public static void exclusiveBetween(double start, double end, double value)
public static void exclusiveBetween(double start, double end, double value, String message)
public static void exclusiveBetween(long start, long end, long value)
public static void exclusiveBetween(long start, long end, long value, String message)
public static <T> void exclusiveBetween(T start, T end, Comparable<T> value)
public static <T> void exclusiveBetween(T start, T end, Comparable<T> value, String message, Object... values)Usage Examples:
public class RangeValidationExamples {
public void setAge(int age) {
Validate.inclusiveBetween(0, 150, age, "Age must be between 0 and 150, got: %d", age);
this.age = age;
}
public void setPercentage(double percentage) {
Validate.inclusiveBetween(0.0, 100.0, percentage, "Percentage must be between 0 and 100");
this.percentage = percentage;
}
public void setTemperature(double celsius) {
Validate.inclusiveBetween(-273.15, 1000.0, celsius, "Temperature must be above absolute zero");
this.temperature = celsius;
}
public void setScore(int score) {
// Exclusive range: score must be > 0 and < 100
Validate.exclusiveBetween(0, 100, score, "Score must be greater than 0 and less than 100");
this.score = score;
}
public void setGrade(String grade) {
Validate.notBlank(grade, "Grade cannot be blank");
// Using Comparable validation with strings
Validate.inclusiveBetween("A", "F", grade, "Grade must be between A and F");
this.grade = grade;
}
// Date range validation
public void setEventDate(Date eventDate) {
Validate.notNull(eventDate, "Event date cannot be null");
Date now = new Date();
Date maxFutureDate = DateUtils.addYears(now, 5);
Validate.inclusiveBetween(now, maxFutureDate, eventDate,
"Event date must be between now and 5 years in the future");
this.eventDate = eventDate;
}
}// Finite number validation (not NaN or Infinity)
public static void finite(double value)
public static void finite(double value, String message, Object... values)
public static void finite(float value)
public static void finite(float value, String message, Object... values)Usage Examples:
public class FiniteValidationExamples {
public void calculateDistance(double x1, double y1, double x2, double y2) {
// Ensure all coordinates are finite numbers
Validate.finite(x1, "x1 coordinate must be finite");
Validate.finite(y1, "y1 coordinate must be finite");
Validate.finite(x2, "x2 coordinate must be finite");
Validate.finite(y2, "y2 coordinate must be finite");
double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
Validate.finite(distance, "Calculated distance is not finite");
return distance;
}
public void setPrice(double price) {
Validate.finite(price, "Price cannot be NaN or infinite: %f", price);
Validate.inclusiveBetween(0.0, Double.MAX_VALUE, price, "Price must be non-negative");
this.price = price;
}
public Money convertCurrency(Money amount, double exchangeRate) {
Validate.notNull(amount, "Amount cannot be null");
Validate.finite(exchangeRate, "Exchange rate must be finite: %f", exchangeRate);
Validate.exclusiveBetween(0.0, Double.MAX_VALUE, exchangeRate, "Exchange rate must be positive");
double convertedValue = amount.getValue() * exchangeRate;
Validate.finite(convertedValue, "Converted amount is not finite");
return new Money(convertedValue, targetCurrency);
}
}// Boolean state validation
public static void isTrue(boolean expression)
public static void isTrue(boolean expression, String message, Object... values)Usage Examples:
public class StateValidationExamples {
public void transferFunds(Account from, Account to, BigDecimal amount) {
Validate.notNull(from, "Source account cannot be null");
Validate.notNull(to, "Destination account cannot be null");
Validate.notNull(amount, "Transfer amount cannot be null");
// Boolean state validation
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0,
"Transfer amount must be positive: %s", amount);
Validate.isTrue(from.getBalance().compareTo(amount) >= 0,
"Insufficient funds: balance=%s, requested=%s", from.getBalance(), amount);
Validate.isTrue(!from.equals(to),
"Cannot transfer to the same account");
Validate.isTrue(from.isActive(),
"Source account %s is not active", from.getAccountNumber());
Validate.isTrue(to.isActive(),
"Destination account %s is not active", to.getAccountNumber());
// Perform transfer...
}
public void processOrder(Order order) {
Validate.notNull(order, "Order cannot be null");
// Validate order state
Validate.isTrue(order.hasItems(), "Order must contain at least one item");
Validate.isTrue(order.getCustomer() != null, "Order must have a customer");
Validate.isTrue(order.getTotalAmount().compareTo(BigDecimal.ZERO) > 0,
"Order total must be positive");
// Validate business rules
Validate.isTrue(order.getItems().size() <= 100,
"Order cannot contain more than 100 items");
Validate.isTrue(order.getTotalAmount().compareTo(new BigDecimal("10000")) <= 0,
"Order total cannot exceed $10,000");
}
public void setUserPermissions(User user, Set<Permission> permissions) {
Validate.notNull(user, "User cannot be null");
Validate.notEmpty(permissions, "Permissions cannot be empty");
// Validate user state
Validate.isTrue(user.isActive(), "Cannot set permissions for inactive user: %s", user.getUsername());
Validate.isTrue(!user.isLocked(), "Cannot set permissions for locked user: %s", user.getUsername());
// Validate permission constraints
boolean hasAdminPermission = permissions.contains(Permission.ADMIN);
boolean hasUserPermissions = permissions.stream().anyMatch(p -> p.getType() == PermissionType.USER);
Validate.isTrue(!hasAdminPermission || user.getRole() == Role.ADMINISTRATOR,
"Only administrators can have admin permissions");
Validate.isTrue(permissions.size() <= 20,
"User cannot have more than 20 permissions");
}
}public final class CustomValidators {
// Email validation
public static String validateEmail(String email) {
Validate.notBlank(email, "Email cannot be blank");
Validate.isTrue(email.contains("@"), "Email must contain @ symbol: %s", email);
Validate.isTrue(email.indexOf("@") != 0, "Email cannot start with @: %s", email);
Validate.isTrue(email.lastIndexOf("@") != email.length() - 1, "Email cannot end with @: %s", email);
Validate.isTrue(email.indexOf("@") == email.lastIndexOf("@"), "Email cannot contain multiple @ symbols: %s", email);
return email.toLowerCase().trim();
}
// Phone number validation
public static String validatePhoneNumber(String phone) {
Validate.notBlank(phone, "Phone number cannot be blank");
// Remove common formatting
String cleaned = phone.replaceAll("[\\s\\-\\(\\)\\.\\+]", "");
Validate.isTrue(cleaned.matches("\\d+"), "Phone number can only contain digits and formatting: %s", phone);
Validate.inclusiveBetween(7, 15, cleaned.length(),
"Phone number must be between 7 and 15 digits: %s", phone);
return cleaned;
}
// URL validation
public static String validateUrl(String url) {
Validate.notBlank(url, "URL cannot be blank");
String lowerUrl = url.toLowerCase();
Validate.isTrue(lowerUrl.startsWith("http://") || lowerUrl.startsWith("https://"),
"URL must start with http:// or https://: %s", url);
try {
new URL(url); // Additional validation
return url;
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL format: " + url, e);
}
}
// Credit card validation (simplified)
public static String validateCreditCard(String cardNumber) {
Validate.notBlank(cardNumber, "Credit card number cannot be blank");
String cleaned = cardNumber.replaceAll("[\\s\\-]", "");
Validate.isTrue(cleaned.matches("\\d+"), "Credit card number can only contain digits: %s", cardNumber);
Validate.inclusiveBetween(13, 19, cleaned.length(),
"Credit card number must be between 13 and 19 digits: %s", cardNumber);
// Luhn algorithm validation
Validate.isTrue(isValidLuhn(cleaned), "Invalid credit card number: %s", cardNumber);
return cleaned;
}
private static boolean isValidLuhn(String cardNumber) {
int sum = 0;
boolean alternate = false;
for (int i = cardNumber.length() - 1; i >= 0; i--) {
int digit = Character.getNumericValue(cardNumber.charAt(i));
if (alternate) {
digit *= 2;
if (digit > 9) {
digit = (digit % 10) + 1;
}
}
sum += digit;
alternate = !alternate;
}
return (sum % 10) == 0;
}
// Password strength validation
public static String validatePassword(String password) {
Validate.notNull(password, "Password cannot be null");
Validate.inclusiveBetween(8, 128, password.length(),
"Password must be between 8 and 128 characters");
Validate.isTrue(password.matches(".*[a-z].*"), "Password must contain at least one lowercase letter");
Validate.isTrue(password.matches(".*[A-Z].*"), "Password must contain at least one uppercase letter");
Validate.isTrue(password.matches(".*\\d.*"), "Password must contain at least one digit");
Validate.isTrue(password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*"),
"Password must contain at least one special character");
return password;
}
}public class FluentValidator<T> {
private final T value;
private final String fieldName;
private FluentValidator(T value, String fieldName) {
this.value = value;
this.fieldName = fieldName;
}
public static <T> FluentValidator<T> validate(T value, String fieldName) {
return new FluentValidator<>(value, fieldName);
}
public FluentValidator<T> notNull() {
Validate.notNull(value, "%s cannot be null", fieldName);
return this;
}
public FluentValidator<T> notEmpty() {
if (value instanceof Collection) {
Validate.notEmpty((Collection<?>) value, "%s cannot be empty", fieldName);
} else if (value instanceof CharSequence) {
Validate.notEmpty((CharSequence) value, "%s cannot be empty", fieldName);
} else if (value instanceof Object[]) {
Validate.notEmpty((Object[]) value, "%s cannot be empty", fieldName);
}
return this;
}
public FluentValidator<T> notBlank() {
if (value instanceof CharSequence) {
Validate.notBlank((CharSequence) value, "%s cannot be blank", fieldName);
}
return this;
}
public FluentValidator<T> inclusiveBetween(Comparable<T> start, Comparable<T> end) {
if (value instanceof Comparable) {
Validate.inclusiveBetween(start, end, (Comparable<T>) value,
"%s must be between %s and %s", fieldName, start, end);
}
return this;
}
public FluentValidator<T> isTrue(boolean condition, String message, Object... args) {
Validate.isTrue(condition, message, args);
return this;
}
public FluentValidator<T> custom(Predicate<T> validator, String message, Object... args) {
Validate.isTrue(validator.test(value), message, args);
return this;
}
public T get() {
return value;
}
}
// Usage examples
public class FluentValidationExamples {
public User createUser(String name, String email, Integer age) {
String validatedName = FluentValidator.validate(name, "name")
.notNull()
.notBlank()
.isTrue(name.length() <= 100, "Name cannot exceed 100 characters")
.get();
String validatedEmail = FluentValidator.validate(email, "email")
.notNull()
.notBlank()
.custom(e -> e.contains("@"), "Email must contain @ symbol")
.get();
Integer validatedAge = FluentValidator.validate(age, "age")
.notNull()
.inclusiveBetween(0, 150)
.get();
return new User(validatedName, validatedEmail, validatedAge);
}
}@ControllerAdvice
public class ValidationExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(ValidationExceptionHandler.class);
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleValidationError(IllegalArgumentException ex) {
log.warn("Validation error: {}", ex.getMessage());
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
ex.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<ErrorResponse> handleNullPointerError(NullPointerException ex) {
log.error("Null pointer error: {}", ex.getMessage(), ex);
ErrorResponse error = new ErrorResponse(
"NULL_VALUE_ERROR",
"A required value was null",
System.currentTimeMillis()
);
return ResponseEntity.badRequest().body(error);
}
}
// Custom validation service
@Service
public class ValidationService {
public <T> T validateAndReturn(T value, Consumer<T> validator, String context) {
try {
validator.accept(value);
return value;
} catch (IllegalArgumentException e) {
throw new ValidationException(context + ": " + e.getMessage(), e);
}
}
public void validateBusinessRules(Object entity) {
if (entity instanceof User) {
validateUser((User) entity);
} else if (entity instanceof Order) {
validateOrder((Order) entity);
} else {
throw new UnsupportedOperationException("Validation not supported for: " + entity.getClass());
}
}
private void validateUser(User user) {
Validate.notNull(user, "User cannot be null");
FluentValidator.validate(user.getEmail(), "email")
.notBlank()
.custom(email -> email.length() <= 255, "Email cannot exceed 255 characters")
.custom(this::isValidEmailFormat, "Invalid email format");
FluentValidator.validate(user.getAge(), "age")
.notNull()
.inclusiveBetween(13, 120); // Business rule: minimum age 13
}
private void validateOrder(Order order) {
Validate.notNull(order, "Order cannot be null");
Validate.notEmpty(order.getItems(), "Order must contain items");
// Validate total amount matches item sum
BigDecimal calculatedTotal = order.getItems().stream()
.map(OrderItem::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Validate.isTrue(order.getTotal().equals(calculatedTotal),
"Order total %s does not match calculated total %s",
order.getTotal(), calculatedTotal);
}
}@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
// Validate request
Validate.notNull(request, "Request body cannot be null");
String name = FluentValidator.validate(request.getName(), "name")
.notBlank()
.isTrue(request.getName().length() <= 100, "Name cannot exceed 100 characters")
.get();
String email = FluentValidator.validate(request.getEmail(), "email")
.notBlank()
.custom(CustomValidators::isValidEmail, "Invalid email format")
.get();
User user = userService.createUser(name, email);
return ResponseEntity.ok(user);
}
}
@Component
public class ConfigurationValidator {
@EventListener
public void validateConfiguration(ApplicationReadyEvent event) {
validateDatabaseConfiguration();
validateCacheConfiguration();
validateSecurityConfiguration();
}
private void validateDatabaseConfiguration() {
String url = environment.getProperty("spring.datasource.url");
Validate.notBlank(url, "Database URL must be configured");
String username = environment.getProperty("spring.datasource.username");
Validate.notBlank(username, "Database username must be configured");
}
}The validation utilities in Apache Commons Lang provide a comprehensive foundation for defensive programming, argument validation, and precondition checking that helps create robust, reliable applications with clear error messages and consistent validation patterns.
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-commons--commons-lang3