Phone Number Validation in Java using Google libphonenumber
Introduction
Phone number validation is a crucial part of many applications, especially those involving user authentication, contact management, and messaging services. Google's libphonenumber provides a powerful API for parsing, formatting, and validating phone numbers across different countries. In this blog, we'll explore how to create a custom annotation for phone validation in Java without using Spring and best practices to make it future-ready.
Why Use Google's libphonenumber?
Google's libphonenumber is widely used for phone number validation due to:
- Global Support: Handles international formats for multiple countries.
- Accuracy: Validates numbers based on country rules.
- Parsing & Formatting: Converts numbers into standardized formats.
- Spam Prevention: Helps detect invalid or improperly formatted numbers.
While many developers use regex for phone validation, it often fails to account for country-specific formats. Google's library provides precise validation and region inference without manual configurations.
Implementing a Custom Annotation for Phone Validation
Since Java does not provide a built-in annotation for phone validation, we need to create a custom annotation and a validator class that integrates libphonenumber
.
Step 1: Create the @ValidPhoneNumber
Annotation
import java.lang.annotation.*;
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhoneNumber {
String message() default "Invalid phone number";
}
This annotation can be used on class fields and method parameters to trigger validation.
Step 2: Create the PhoneNumberValidator
Class
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import com.google.i18n.phonenumbers.NumberParseException;
import java.lang.reflect.Field;
public class PhoneNumberValidator {
public static boolean isValid(Object object) {
try {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ValidPhoneNumber.class)) {
field.setAccessible(true);
String phoneNumber = (String) field.get(object);
if (phoneNumber != null && !phoneNumber.isEmpty() && !validatePhoneNumber(phoneNumber)) {
System.out.println(field.getAnnotation(ValidPhoneNumber.class).message());
return false;
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return true;
}
private static boolean validatePhoneNumber(String phoneNumber) {
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
try {
Phonenumber.PhoneNumber numberProto = phoneUtil.parse(phoneNumber, "ZZ"); // "ZZ" allows automatic region detection
return phoneUtil.isValidNumber(numberProto);
} catch (NumberParseException e) {
return false;
}
}
}
This validator:
✅ Processes fields annotated with @ValidPhoneNumber
✅ Uses libphonenumber
to check validity
✅ Handles optional numbers (skips validation if phone is empty)
✅ Provides flexibility for future enhancements
Step 3: Use the Annotation in a Java Class
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.GroupSequence;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.bhn.resource.constants.Constants;
import org.bhn.resource.validator.UniqueUsername;
import org.hibernate.validator.constraints.Length;
import javax.ws.rs.FormParam;
import javax.xml.bind.annotation.XmlRootElement;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@XmlRootElement
@GroupSequence({ NotNull.class, NotEmpty.class, Pattern.class, Length.class, UniqueUsername.class, User.class})
public class User {
@FormParam("phone")
@ValidPhoneNumber
private String phone;
}
Best Practices for Phone Validation
🔹 Use libphonenumber
instead of regex: Regular expressions often fail for international formats.
🔹 Ensure numbers are stored in E.164 format: This ensures consistency across systems
. 🔹 Handle optional fields: Skip validation if phone number is empty.
🔹 Avoid hardcoding regions: Use "ZZ" for automatic region detection when possible.
🔹 Enhance error handling: Provide meaningful error messages instead of generic exceptions.
Future Enhancements
✅ Integrate with REST APIs: Extend validation to API requests using a microservices architecture. ✅ Use dependency injection: Instead of static methods, use dependency injection for better testability. ✅ Enhance error reporting: Collect logs and statistics to track validation failures. ✅ Validate using carrier databases: Some providers offer APIs to check if a number is active. ✅ Implement caching: Reduce redundant validations by caching previously validated numbers.
Conclusion
Google's libphonenumber
is the best solution for handling phone number validation in Java. By implementing a custom annotation, we ensure that our applications handle phone numbers accurately and efficiently. This approach is scalable, maintainable, and future-proof, making it a great choice for developers working on applications that require phone validation.
Next Steps
🚀 Try implementing this in a microservice and validate phone numbers in a REST API. 🔎 Explore carrier validation APIs to check if a number is active. 📈 Optimize performance by integrating caching mechanisms.
For more advanced tutorials, stay tuned! Happy coding! 😃