I have spent quite some time debugging an issue where a phone number that was entered, formatted and validated on an Android device was rejected by the backend server because it could not be used to send an SMS.

It turns out formatting and validating phone numbers can be a bit tricky, depending on the phone number and the specific locale of the device.

What was happening?

The user enters his phone number into an EditText that has TextWatcher attached to it. The TextWatcher is a standard Android PhoneNumberFormattingTextWatcher and automatically formats the phone number while typing.

phoneEditText.addTextChangedListener(new PhoneNumberFormattingTextWatcher(Locale.getDefault().getCountry()));

When the user is done, the number is reformatted in the international E164 format using PhoneNumberUtils.formatNumberToE164 before it is sent to the backend.

PhoneNumberUtils.formatNumberToE164(phone, Locale.getDefault().getCountry());

Note that both the PhoneNumberFormattingTextWatcher() and PhoneNumberUtils.formatNumberToE164() require a country code to figure out if a phone number is valid and how it should be formatted.

When a Belgian user types the phone number 0475873235, he will see 0475 87 32 35, and the backend will receive the full E164 number +32475873235. This is a valid mobile number in Belgium and can be used to send an SMS.

This example works fine when the locale of the user’s device is set to the Belgian locale nl_BE. Unfortunately things start to go wrong when the user has set his phone to nl_NL instead of nl_BE. Both locales are largely the same (e.g. the UI is shown in Dutch, date and amounts are formatted the same way,…) but they do phone number formatting differently.

On an nl_NL device, when the user types the phone number 0475873235, he will still see 0475 87 32 35 because that phone number is also valid in the Netherlands. But this time the backend will receive the full E164 number +31475873235, with the international prefix for the Netherlands: 31. Even though this is a valid number in the Netherlands, it is not a valid mobile number and can not be used to send an SMS. The server encounters a problem and returns an error.

Locale User types User sees Backend gets Backend sends SMS
en_US 0475873235 Invalid number N/A N/A
nl_BE 0475873235 0475 87 32 35 +32475873235 Success
nl_NL 0475873235 0475 87 32 35 +31475873235 Failure

The solution

Since quite a few people in Belgium have set up their phone as nl_NL instead of nl_BE, it’s best to avoid Locale.getDefault().getCountry().

Instead I decided to use the country code provided by TelephonyManager.getNetworkCountryIso(). This returns the country code of the current registered operator of the cell tower nearby.

public String getPhoneCountry() {
    TelephonyManager tm = ContextCompat.getSystemService(context, TelephonyManager.class);
    if (tm != null && !TextUtils.isEmpty(tm.getNetworkCountryIso())) {
        return tm.getNetworkCountryIso().toUpperCase(Locale.US);
    } else {
        return Locale.getDefault().getCountry();
    }
}

Note that this code has a fallback to Locale.getDefault().getCountry() when the operator’s country code is not available.

Updated: