Phone Number Validation using custom Hibernate Validators

Phones

I’m sure most developers have been there: you have to develop a seemingly simple piece of work. You build it, you test it, you bring it to production, and a few weeks later you run into a bug. This is what happened to us, in a seemingly straightforward piece of code: accepting phone numbers in our REST endpoint.

Spring Boot with Hibernate Validator

Most of our REST endpoints are written in Spring Boot. For our input validation, we mostly use Hibernate Validator. Using Hibernate Validator, not to be confused with the Hibernate ORM, is a good practice to prevent issues like Injection Vulnerabilities. Examples of such are SQL injections, ORM injections, Buffer overflows, etc. Besides that, Hibernate Validator is a good framework to support normal business flows, such as checking the lengths of fields and formatting of certain input, like names, email addresses, and phone numbers.

Simple Phone Number Validation

Let’s assume we want to validate phone number for Australia. The logic would be similar to any country, but since Australia is the country where I’m currently located, I’ll use that for now. Valid Australian phone numbers look like this:

Mobile phone numberStarts with 0400403123456
Australian phone number, eg SydneyStarts with 0280281234567
International phone numberStarts with +61+61403123456

In general, we could say that a phone number starts with either a 0 or with +61, followed by 9 digits. We could use Regular Expressions to validate the input. The Regular Expression used will be: (\+61|0)[0-9]{9}.

The pattern is explained below:

  (\+61|0)    # Start with either +61 or 0, and escape the +
  [0-9]{9}    # Then 9 numbers in the range from 0 till 9

Using Hibernate Validator, an easy way to create a phone number validation mechanism would be to use the Pattern annotation. In code, including another escape, this would look like the following:

@Pattern(regexp = "(\\+61|0)[0-9]{9}").

You can use it to annotate an instance field like so:

import javax.validation.constraints.Pattern;

@Pattern(regexp = "(\\+61|0)[0-9]{9}")
private String phoneNumber;

However, there are a few downsides with the above. The regular expression is quite simple, which makes the expression reasonably readable. However, it’s not very flexible. What if the phone number contains a dash or parenthesis? Or what if there’s a letter in the number? What if we need to support the 6-digit phone numbers which are also used in Australia? Suddenly, our phone number validation becomes slightly more challenging.

And since we’re not a fan of reinventing the wheel, let’s see what we can do.

What is libphonenumber

To address the issue above, Google has created a library called libphonenumber, a library used in Android to handle phone numbers. Libphonenumber is a library for Java, C++, and Javascript and can be used for formatting, parsing, and validating phone numbers. By using an internal database of geocoding information, libphonenumber is able to provide country-specific information about phone numbers, such as region codes, carriers, and much more. A good example of libphonenumbers capabilities can be seen on the libphonenumber demo page, in which inputting a phone number of +61403476123 will result in the following parsed data:

getCountryCode()61
getNationalNumber()403476123
getExtension() 
getCountryCodeSource()FROM_NUMBER_WITH_PLUS_SIGN
isItalianLeadingZero()false
getRawInput()+61403476123

When trying to get some information about the parsed number, such as the phone number type or if the number is valid, something we’ll need later, the following methods are available:

isPossibleNumber()true
isValidNumber()true
isValidNumberForRegion()false
getRegionCodeForNumber()AU
getNumberType()MOBILE

Using libphonenumber with Hibernate Validation

Now that we have a bit of our problem and a possible solution, let’s see how we can put things together. For the final solution we’ll use Kotlin, but Java, Groovy or Scala would be valid choices too. In this part, we’ll create a small Spring Boot application which will validate phone numbers, and for a valid phone number, it will format the phone number according to the E.164 standard.

Controller layer

To start with, let’s create a simple controller, which will take in a DTO annotated with JSR-380, also known as Bean Validation 2.0, for which Hibernate Validator 6.x is the reference implementation.

@RestController
class PhoneNumberController {

    @PostMapping("/phonenumber")
    fun validate(@RequestBody @Valid input: PhoneNumber): ResponseEntity<Message> {
        return ResponseEntity.ok(Message("Number is valid: ${input.formatPhoneNumber()}"))
    }

    data class Message(val message: String)
}

data class PhoneNumber(
        @field:PhoneNumberConstraint
        val phoneNumber: String
) {
    fun formatPhoneNumber(): String {
        val instance = PhoneNumberUtil.getInstance()
        val number = instance.parse(phoneNumber, "AU")
        return instance.format(number, PhoneNumberUtil.PhoneNumberFormat.E164)
    }
}

The above code will take a validated PhoneNumber data class as an input. If it’s valid, it will format the phone number in using the E.164 standard and return it to the user. As you can see in the code above, the PhoneNumber is using the Australian (“AU”) locale to parse the number.

Hibernate Custom Phone Number Constraint

This code on its own won’t work since it’s missing the PhoneNumberConstraint validation, which is doing the actual validation work.

import javax.validation.Constraint
import javax.validation.Payload
import kotlin.reflect.KClass

@MustBeDocumented
@Constraint(validatedBy = [PhoneNumberValidator::class])
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class PhoneNumberConstraint(
        val message: String = "Invalid phone number",

        val groups: Array<KClass<*>> = [],

        val payload: Array<KClass<out Payload>> = []
)

Annotations written in Kotlin use slightly different meta annotations compared to Java annotations. For example, the Kotlin version uses the @MustBeDocumented annotation compared to @Documented, and also @Target and @Retention use specific Kotlin annotations.

When using the above annotation on a field as seen in the PhoneNumber data class, validation is triggered. Therefore, whenever a phone number is invalid, the ‘Invalid phone number’ message is triggered. This can be used as feedback to the user.

Conclusion

As we’ve seen in this blog post, validating phone numbers can be deceivingly tricky. However, libraries like libphonenumber, especially when combined with Hibernate Validator, can prove to be a benefit to the developer. For the full source of this blog post, please have a look at the Github repository.

Leave a Reply

Your email address will not be published. Required fields are marked *