Validating phone numbers in a Spring Boot app using the Twilio Lookup API

April 06, 2020
Written by

Validating phone numbers in a Spring Boot app using the Twilio Lookup API

Many user registration flows these days require a user to provide a phone number, either for two-factor authentication or to provide notifications and updates.

To check that the number provided by your user is valid, you might reach for a regular expression, but that won’t be reliable - phone numbers are complicated, and it won’t tell you if the number provided is actually reachable. There are multiple levels of validation possible - in this post I’ll show how to use the Twilio Lookup API to check if a real phone number is real.

The demo application will use Spring Boot and I will show how to use the Bean Validation framework to create a @ValidPhoneNumber annotation that can be used like you would @NotNull.

Starting out

What you will need before you start

  • A Twilio account. Sign up using this link and you can get an extra $10 credit when you upgrade your account.
  • Java installed - use Java 8 or newer. My preferred way to install Java is with SDKMAN!
  • git

Start out by cloning the demo repository from github at the no-validation tag:

git clone git@github.com:mjg123/twilio-lookup-api-spring-boot-demo.git -b no-validation
cd twilio-lookup-api-spring-boot-demo

Start the application with ./mvnw spring-boot:run. You’ll know it’s up and running when you see the log messages stop at a line containing Started PhoneNumberLookupDemoApplication.

Now you can browse to http://localhost:8080 and you should see this:

UI for this phone validator - a web page with fields for "name" and "phone number"

Try hitting “Submit” without a name or phone number and you will see error messages. Entering values in both fields will show you the other page in this app:

The app UI where someone has successfully used "Haha - this isn't my phone number" as a phone number. We need to fix this.

Let’s check how that validation was configured in the server code. Using your IDE open up the Person class in the lol.gilliard.twiliolookupdemo package, and you’ll see this section of code:

@NotNull
@NotBlank(message="Please enter your name")
private String name;

@NotNull
@NotBlank(message="Please enter your phone number")
private String phoneNumber;

These annotations are part of the Bean Validation framework that Spring uses when creating a Person object from the fields in the form. The problem is that by only using @NotBlank on the phoneNumber field the app doesn’t actually make sure that the phone number is even a number, never mind a valid phone number.

For better validation, it’s possible to create a custom annotation @ValidPhoneNumber, that uses the Twilio Lookup API to verify that a phone number is real.

Adding better validation with the Twilio Lookup API

You will need to create two new classes and some configuration:

  • an annotation called ValidPhoneNumber which can be used to tag the phoneNumber field in the Person class.
  • a class which implements ConstraintValidator that contains the code which performs the validation.
  • your Twilio account details are needed for authentication by the Lookup API

Then you can use that annotation in the Person class.

Creating the annotation

In the same package as the other classes, create a new file called ValidPhoneNumber.java to hold the code for the annotation. Annotations are declared with the @interface keyword. The code for the annotation is:

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidPhoneNumber {
   String message() default "doesn't seem to be a valid phone number";
   Class<?>[] groups() default {};
   Class<? extends Payload>[] payload() default {};
}

[full code, including imports on GitHub]

This is the standard way to create an annotation for the Validation framework - it may seem complex but the two most important things are highlighted:

  • the @Constraint annotation which specifies the class which contains the code for performing the validation
  • the default message, which can be overridden as you’ll see later

Creating the validator

Create the PhoneNumberValidator class in the same package, and give it this content:

@Configurable
public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {

   @Value("${TWILIO_ACCOUNT_SID}")
   private String twilioAccountSid;

   @Value("${TWILIO_AUTH_TOKEN}")
   private String twilioAuthToken;

   @Override
   public void initialize(ValidPhoneNumber constraintAnnotation) {
       Twilio.init(twilioAccountSid, twilioAuthToken);
   }

   @Override
   public boolean isValid(String value, ConstraintValidatorContext context) {

       // The Lookup API requires your phone number in E.164 format
       // E.164 formatted phone numbers must not have spaces in them
       value = value.replaceAll("[\\s()-]", "");

       if ("".equals(value)){
           return false;
       }

       try {
           PhoneNumber.fetcher(new com.twilio.type.PhoneNumber(value)).fetch();
           return true;

       } catch (ApiException e){
           // The Lookup API returns HTTP 404 if the phone number is not found
           // (ie it is not a real phone number)
           if (e.getStatusCode() == 404){
               return false;
           }
           throw e;
       }
   }
}

[full code, including imports on GitHub]

The isValid method needs to return true if the value is a valid phone number, and there’s a few steps to doing that. First of all we need to strip unnecessary characters from the number that the user entered:

       // The Lookup API requires your phone number in E.164 format
       // E.164 formatted phone numbers must not have spaces in them
       value = value.replaceAll("[\\s()-]", "");

It’s common to write phone numbers with spaces, brackets and dashes to group the digits, but the Lookup API expects input in E.164 format, which is a standard format to represent international phone numbers that starts with a + and includes only numbers - no other spaces or special characters.

Next, if there is no input from the user it’s safe to say it’s not a valid phone number, so return early to save an API call. Comparing to an empty string is fine here, but if we were using Java 11 then String#isBlank() would be appropriate:

       if ("".equals(value)){
           return false;
       }

It’s time to call the Lookup API. This is done in this code:

           PhoneNumber.fetcher(new com.twilio.type.PhoneNumber(value)).fetch();
           return true;

If the phone number is not valid, the Lookup API returns a 404 which is surfaced in code as an ApiException, so if we reach the return true then it was a valid phone number. If not we enter the catch block and return false if the response code was 404.

Configuring the Twilio client

Add your Twilio account credentials to the app. Fill in the values in src/main/resources/application.properties - you can find them on your Twilio console.

TWILIO_ACCOUNT_SID=AC.....
TWILIO_AUTH_TOKEN=......

These values will be injected into the PhoneNumberValidator at runtime by Spring.

Using the validator

Finally you are ready to use the new validator you created. In the Person class, replace:

@NotNull
@NotBlank(message="Please enter your phone number")
private String phoneNumber;

with

@NotNull
@ValidPhoneNumber(message="Please enter a valid phone number")
private String phoneNumber;

This also shows how you can override the default validation failure message.

Testing it out

It’s time to restart your application and test it again. Stop the server that you ran before and restart it using ./mvnw spring-boot:run. Now you should only be able to proceed by entering a valid phone number, remembering the international dialling code if you aren’t in the USA.

The app UI again, phone number validation is now working as shown by "123456789" being rejected

Wrapping up

By following along with this post you have built a Spring Boot application with custom validation using the Bean Validation Framework that calls the Twilio Lookup API to verify that a phone number is real. No small task - pat yourself on the back.

Of course this code does not check that the phone number actually belongs to the person filling out the form. To learn more about that check out the Verify API and learn more about how Twilio can help with security by checking out Authy.

If you want to build more with Twilio and Java check out our quickstarts and the Java content on our blog. As always, I’m excited to see what you build - reach out on Twitter at @MaximumGilliard or by email on mgilliard@twilio.com