March 4, 2025

Phone Number Validation in Java using Google libphonenumber

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 @ValidPhoneNumberUses libphonenumber to check validityHandles 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! 😃