# Does libphonenumber check if a phone number is active? isValidNumber vs reachable

> No. libphonenumber checks numbering-plan metadata, not the network. What isValidNumber proves, what it can't, and how to check if a number is live.

Canonical: https://mobilevalidate.com/blog/does-libphonenumber-check-if-a-number-is-active · Last updated: 2026-10-10

![A six-rung ladder: a code panel ticks the two bottom rungs, possible and valid, offline, while the four upper rungs, assigned, active, ported and apps, stay open until a network tower answers them.](https://mobilevalidate.com/images/blog/does-libphonenumber-check-if-a-number-is-active.svg)

*A validation library answers the first two rungs; only the network knows if a number is assigned and live.*


By MobileValidate team (https://mobilevalidate.com/about) · Published: 2026-10-10 · Category: Developers · Tags: Libphonenumber, Phone validation, Number reachability, HLR lookup, Javascript, Python

No. libphonenumber checks whether a phone number fits its country's numbering plan, using metadata that ships with the library. It never contacts a network, so it can't tell you whether the number has been issued to anyone, whether the phone is switched on, which network serves it after a port, or whether it's registered on a messaging app. `isValidNumber` returning `true` means "this number could exist", not "this number is active". To find out whether a number is live, send the validated E.164 number to an [HLR lookup](/services/hlr-lookup), which asks the number's home network.

## What does libphonenumber actually check?

Google's [libphonenumber](https://github.com/google/libphonenumber) packages every country's numbering plan as metadata: country codes from [ITU-T E.164](https://www.itu.int/rec/T-REC-E.164/en), lengths, and the digit ranges each country has opened for each kind of service. Its main functions read that metadata and nothing else:

- **`isPossibleNumber`** "quickly guesses whether a number is a possible phone number by using only the length information" ([libphonenumber README](https://github.com/google/libphonenumber)).
- **`isValidNumber`** does "full validation of a phone number for a region using length and prefix information".
- **`getNumberType`** returns the type of the range the number falls in, such as `MOBILE`, `FIXED_LINE`, `TOLL_FREE` or `VOIP`, or `FIXED_LINE_OR_MOBILE` where the plan doesn't separate them, as in the United States.
- **The carrier mapper** (`PhoneNumberToCarrierMapper` in Java, `phonenumbers.carrier` in Python) returns a carrier name for some mobile ranges.

The library's [FAQ](https://github.com/google/libphonenumber/blob/master/FAQ.md) defines a valid number range as "one from which numbers can be freely assigned by carriers to users", and then says it plainly: "Do not rely on libphonenumber to determine whether numbers are currently assigned to a specific user and reachable." The FAQ adds that confirming reachability needs a verification step, such as an SMS or a call.

The carrier mapper has the same limit. Its documentation says the name "is the one the number was originally allocated to, however if the country supports mobile number portability the number might not belong to the returned carrier anymore" ([PhoneNumberToCarrierMapper](https://github.com/google/libphonenumber/blob/master/java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java)). That is why `getSafeDisplayName` returns an empty string for any region with [mobile number portability](/glossary/mobile-number-portability).

## Which questions can libphonenumber answer, and which need the network?

Think of it as a ladder. libphonenumber answers the first two rungs for free, offline, in microseconds. Every rung above that needs live data.

| Rung | Question | Answered by | libphonenumber? |
|---|---|---|---|
| 1. Possible | Is the length right for the country? | `isPossibleNumber` | Yes |
| 2. Valid | Is it in a range the numbering plan opens for assignment? | `isValidNumber`, `getNumberType` | Yes |
| 3. Assigned | Has an operator issued this number to a subscriber? | The operator's records, via an HLR lookup (`invalid` = not assigned) | No |
| 4. Active on the network | Can the phone be reached right now? | [HLR lookup](/services/hlr-lookup): `reachable` or `unreachable` | No |
| 5. Ported | Which network serves it today? | [MNP lookup](/services/mnp-lookup) or HLR lookup | No, only the original operator |
| 6. Registered on apps | Is it registered on a messaging app? | [Registration checks](/blog/messaging-app-registration-checks-guide) | No |

Two side questions also need outside data: the current line type ([carrier lookup](/services/carrier-lookup)) and the number's reputation ([spam reputation](/services/spam-reputation)). We compare rungs 3 to 5 in [HLR vs MNP vs number validation](/blog/hlr-vs-mnp-vs-number-validation), and the [guide to checking if a phone number is active](/blog/how-to-check-if-a-phone-number-is-active) covers every meaning of "active".

## What does validation look like in code?

The calls are the same in every port. We ran the JavaScript and Python examples below on 2026-10-10 with libphonenumber-js 1.13.13 and phonenumbers 9.0.40; the outputs are real.

**JavaScript** with [libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js). Import from `/max` on the server: with the default min metadata, `isValid()` checks mostly lengths and leading digits, and `getType()` usually returns `undefined`. libphonenumber-js has no carrier-name lookup. More in our [JavaScript validation guide](/blog/phone-number-validation-javascript).

```js
import { parsePhoneNumberFromString } from "libphonenumber-js/max";

const p = parsePhoneNumberFromString("+12001230101");
p.isPossible(); // true  (right length for +1)
p.isValid();    // false (area code 200 is not in use)

const m = parsePhoneNumberFromString("+447911123456");
m.isValid();    // true
m.getType();    // "MOBILE"
m.number;       // "+447911123456": store this, then check the network
```

**Python** with [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers). The carrier module shows the portability problem: a name for the range, but no "safe" name, because Romania has number portability. See the [Python validation guide](/blog/phone-number-validation-python).

```python
import phonenumbers
from phonenumbers import carrier, PhoneNumberFormat

n = phonenumbers.parse("+40721234567", None)
phonenumbers.is_valid_number(n)          # True
phonenumbers.number_type(n)              # 1 (PhoneNumberType.MOBILE)
carrier.name_for_number(n, "en")         # 'Vodafone' (original operator of the range)
carrier.safe_display_name(n, "en")       # ''  (Romania has number portability)
phonenumbers.format_number(n, PhoneNumberFormat.E164)  # '+40721234567'
```

**Java** with Google's library (`com.googlecode.libphonenumber:libphonenumber`, plus the separate `carrier` artifact for the mapper). Details in the [Java and Kotlin guide](/blog/phone-number-validation-java-kotlin).

```java
import com.google.i18n.phonenumbers.*;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.util.Locale;

PhoneNumberUtil util = PhoneNumberUtil.getInstance();
PhoneNumber n = util.parse("+40721234567", null); // throws NumberParseException
boolean valid = util.isValidNumber(n);
PhoneNumberUtil.PhoneNumberType type = util.getNumberType(n);
String e164 = util.format(n, PhoneNumberUtil.PhoneNumberFormat.E164);

PhoneNumberToCarrierMapper carriers = PhoneNumberToCarrierMapper.getInstance();
String original = carriers.getNameForNumber(n, Locale.ENGLISH);  // range's original operator
String safe = carriers.getSafeDisplayName(n, Locale.ENGLISH);    // "" in portable regions
```

Not one of these calls tells you if `+40721234567` is in service. They can't: the answer lives in the operator's subscriber register.

## How do you check whether a valid number is reachable?

Send the E.164 number to an HLR lookup. It asks the number's home network and returns `status`: `reachable`, `unreachable` (assigned, but the phone is off or out of coverage), `invalid` (the network reports it as not assigned) or `unknown`. It also returns `ported`, `roaming` (true or false only), `network`, `mcc_mnc` and `country`.

With a test key, `+447700900002` is a documented test number that returns `unreachable`. Test calls are free and never reach a network; see [test mode](/docs/test-mode). libphonenumber reports this range as invalid on purpose, because the UK regulator reserves it for drama, so let it through your validator only when you use a test key.

```bash
curl https://api.mobilevalidate.com/v1/lookup \
  -H "Authorization: Bearer $MOBILEVALIDATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numbers": ["+447700900002"], "checks": ["hlr"]}'
```

Response (excerpt, test mode):

```json
{
  "e164": "+447700900002",
  "number_status": "valid",
  "checks": {
    "number.hlr": {
      "status": "completed",
      "attributes": {
        "status": "unreachable",
        "ported": false,
        "roaming": false,
        "network": "Test Mobile",
        "mcc_mnc": "23415",
        "country": "GB"
      },
      "billed": false
    }
  }
}
```

The API runs rungs 1 and 2 itself: input that isn't a valid number comes back as `number_status: "invalid_number"` and is never checked or billed. Treat `unknown`, `pending` and `unsupported_country` as "no answer", never as "inactive". The [HLR lookup results guide](/blog/hlr-lookup-results-explained) explains each status.

## When should you use libphonenumber, and when a live check?

| Moment | What to run | Why |
|---|---|---|
| Sign-up form, while typing | libphonenumber (min metadata is fine in the browser) | Instant feedback, no cost |
| Sign-up form, on submit | libphonenumber `/max` on the server, store E.164 | Client checks can be bypassed |
| Before sending an OTP | Validation, then an HLR lookup with `max_age: 0` | Offer another channel at once if the number is `unreachable` or `invalid` |
| Cleaning a list | Validation on every row, then a bulk HLR lookup on the valid ones | Drop impossible numbers for free; pay only for the network check |
| Routing SMS by network | MNP lookup | Current network for ported numbers, cheaper than HLR |

**Cost.** libphonenumber is free. Our HLR and MNP lookups are paid per conclusive answer, at the real-time or bulk price on the [pricing](/pricing) page, and the MNP lookup costs less than the HLR lookup. For the HLR lookup, `reachable`, `unreachable` and `invalid` are billed; `unknown` is free. Invalid input, duplicates and repeat checks served from your account's cache aren't charged, test keys are never billed, and `POST /v1/jobs/estimate` shows the maximum cost of a list before it runs.

## What mistakes do developers make with isValidNumber?

1. **Reading "valid" as "exists".** A valid number may never have been issued, or may have been disconnected and is waiting to be reissued. Only the network knows.
2. **Validating on the server with min metadata.** In libphonenumber-js, `isValid()` with the default import checks mostly lengths and leading digits, not exact ranges. Use `libphonenumber-js/max` for the final check.
3. **Showing the carrier mapper's name as the current carrier.** It is the range's original operator. After a port it is wrong, silently. Use `getSafeDisplayName` if you must display it, and an MNP lookup if you route on it.
4. **Treating `getNumberType` as the live line type.** The type belongs to the range. `FIXED_LINE_OR_MOBILE` is common in North America, and a number ported to a VoIP service keeps its old range.
5. **Letting the metadata go stale.** Numbering plans change every few weeks. An old version rejects newly opened ranges, as the libphonenumber-js README warns. Update the library regularly.
6. **Turning a missing answer into "inactive".** An HLR `unknown` or a timeout means no answer. Retry later or fall back; don't block a real user on it.
7. **Sending HLR lookups for landlines.** Only mobile numbers have a home register to ask. Check the line type first if your list mixes both.

Check only numbers you have a lawful reason to process, such as your own users and people who gave you their number; the [acceptable use policy](/legal/acceptable-use) applies to every lookup.

## What is the short answer?

- libphonenumber answers "could this number exist?" from metadata: possible, valid and range type.
- It never answers "is it assigned?", "is it on right now?", "which network serves it today?" or "is it on an app?".
- Its carrier name is the original operator and can be wrong after a port.
- Validate first because it's free, then use an HLR lookup when you need to know the number is live.

To try the live step, [get a free test key](/get-test-key) and run the request above.

## Sources

1. [libphonenumber FAQ: What does it mean for a phone number to be valid?](https://github.com/google/libphonenumber/blob/master/FAQ.md) — Google, 2026
2. [libphonenumber README](https://github.com/google/libphonenumber) — Google, 2026
3. [PhoneNumberToCarrierMapper (getNameForNumber, getSafeDisplayName)](https://github.com/google/libphonenumber/blob/master/java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java) — Google libphonenumber, 2026
4. [libphonenumber-js README](https://github.com/catamphetamine/libphonenumber-js) — GitHub (catamphetamine), 2026
5. [python-phonenumbers README](https://github.com/daviddrysdale/python-phonenumbers) — GitHub (daviddrysdale), 2026
6. [Recommendation ITU-T E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en) — ITU, 2010

## Frequently asked questions

### Does isValidNumber mean the phone number is in service?

No. isValidNumber means the digits fall inside a range that the country's numbering plan opens for assignment. It does not know whether an operator has issued the number to anyone, or whether the phone is switched on. libphonenumber's own FAQ says not to rely on it for that.

### What is the difference between isPossibleNumber and isValidNumber?

isPossibleNumber checks only the length for the country. isValidNumber also checks the leading digits against the ranges in the metadata. +1 200 123 0101 has the right length for the United States, so it is possible, but it is not valid, because area code 200 is not in use.

### Can libphonenumber tell me the current carrier of a number?

No. The carrier mapper in the Java library and the Python port returns the operator the range was originally allocated to. In countries with number portability that can be wrong, and getSafeDisplayName returns an empty string there. libphonenumber-js has no carrier-name lookup at all.

### How do I check if a phone number is active, then?

Validate it with libphonenumber first, which is free and offline. Then send the E.164 number to an HLR lookup, which asks the number's home network whether it is reachable, unreachable or not assigned. Use an MNP lookup if you only need the current network.

### Does libphonenumber-js /max check whether a number exists?

No. The max metadata makes isValid() check exact number ranges and lets getType() return a type, but it is still metadata. A number can pass isValid() with /max and still be unassigned, disconnected or switched off.
