libphonenumber-android exists because getResourceAsStream is slow on Android
Android port of Google's libphonenumber
At a glance
- What is it?
- libphonenumber-android is Michael Rozumyanskiy's Android port of Google's libphonenumber, which repackages the classes under io.michaelrocks.libphonenumber.android and replaces the slow resource loading path with something that works on Android. It is a drop-in with three documented differences.
- Who is it for?
- Adopt libphonenumber-android if your Android app needs phone number parsing, formatting and validation and you have measured the original library being slow on device, or you simply do not want to work around it. Do not adopt it if you are off Android, since it is an Android port with an Android shaped API, and note that it is not fully compatible with the upstream library.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 27 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One problem, stated in a sentence
libphonenumber-android is an Android port of Google's libphonenumber, and the README explains the reason for the fork in unusually direct terms.
Google's libphonenumber is a great library but it has a major flaw when used on Android. Internally the library uses the Class.getResourceAsStream method, which is very slow on Android. The goal of this library is to fix these two issues.
So the entire justification is performance, and it is one specific performance problem. A library that reads its metadata, the phone number metadata for every region, off the classpath at runtime pays a cost that a desktop JVM does not pay in the same way, because Android's class loading and resource access go through a different path. The linked reference in the README is a Nimbledroid blog post about slow ClassLoader.getResourceAsStream, which tells you the problem was measured rather than assumed.
What the port is not is a rewrite. It is the same library, repackaged and adjusted so the metadata loads in a way that Android handles well. That matters for maintenance, because the upstream library continues to receive updates and this port has to follow it.
The primary language is Java, the licence is Apache-2.0 with a copyright line for Michael Rozumyanskiy dated 2025, and the repository is not archived with the last push on 2026-09-25. There are no GitHub releases, so the version that matters is the one you pin in your build file.
The Gradle dependency, and the version you pin
Adding the library is a normal Gradle dependency on Maven Central.
repositories {
mavenCentral()
}
dependencies {
implementation 'io.michaelrocks:libphonenumber-android:9.0.40'
}The coordinates are worth noting because they differ from upstream in two ways. The group is io.michaelrocks rather than com.google.i18n, and the artifact name carries the android suffix. The version, 9.0.40, tracks the upstream libphonenumber versioning, which is the practical signal that this is a port of a particular upstream release rather than an independently versioned library.
The repository layout is consistent with a Gradle project that produces an Android library and a sample application. There is a library/ directory, a sample/ directory with its own build.gradle.kts, proguard-rules.pro and src/, a buildSrc/ directory, build.gradle.kts and settings.gradle.kts, the gradlew wrapper, a debug.keystore, and an update.sh script at the top level, which is presumably how the port tracks upstream.
The sample/ directory is more than a demo. It gives you a working build.gradle.kts with the dependency and the ProGuard rules, so it doubles as the answer to the question every adopter has, which is how to configure this in an app that minifies.
Three API differences, and the one that will bite you
The README is explicit that this library is not fully compatible with the original libphonenumber, and it lists three differences.
First, every libphonenumber class is repackaged to io.michaelrocks.libphonenumber.android. That is a namespace change, which means imports change and any code you copy from upstream examples changes with them. It also means this library cannot be on the classpath alongside upstream libphonenumber without a class name clash, so it is an either or decision.
Second, and this is the one that catches people out, PhoneNumberUtil does not contain a getInstance method, so you have to create an instance of the class with one of the PhoneNumberUtil.createInstance methods and store it somewhere. The original library's static singleton is gone, which means the instance lifecycle is your problem, and on Android that is a real question because the factory method takes a Context.
Third, PhoneNumberUtil now has a createInstance method taking a Context, which the README calls the default way to obtain an instance of this class on Android.
So the port trades a static singleton for a factory that needs a Context. The second and third differences are the same change seen from two sides, and together with the repackaging they are the entire practical difference in how you call the library.
DexGuard users have one extra line, and everyone else has none
There is a short section for DexGuard users, and it is worth reading even if you do not use DexGuard, because it tells you how the library loads its resources.
If your project is obfuscated with DexGuard you may need to add the following line to the DexGuard configuration.
-keepresourcefiles assets/io/michaelrocks/libphonenumber/android/**The path matches the repackaged namespace, which confirms that the metadata is loaded as assets under the library's own package path rather than through the classpath mechanism that upstream uses. That is the whole fix in one line: the same data, read from somewhere Android is fast at.
For a project using ProGuard or R8 rather than DexGuard, the README does not give a separate section, and the sample directory carries proguard-rules.pro for you to look at. That is a sensible arrangement: the common case needs no special configuration, and the uncommon one is documented in the place where the obfuscator names are different.
So the adoption cost of this port is, for most Android developers, one dependency line and a namespace change. The DexGuard case is a config line. Neither is dramatic, which is the strongest argument for the library existing at all.
Running the checks, and the emulator tests
The Development section documents two Gradle tasks, one for unit checks and one for the integration tests on an emulator.
./gradlew check./gradlew integrationTestThe integration test is described as running on the Gradle-managed local emulator, which is a specific and useful detail. A Gradle managed emulator means the test does not depend on a hand configured AVD on your machine, which removes the most common reason a team's integration tests never get run.
The build itself is visible in the repository. There is a GitHub Actions workflow for the build, with a badge in the README, and the build uses a Kotlin DSL with buildSrc/ for custom build logic, gradle.properties for configuration and the standard gradlew and gradlew.bat wrappers.
The README does not describe what the tests cover, whether the port tracks upstream test cases, or how the metadata is packaged in the artefact. Those are the questions the build and the update script would answer, and for an adopter comparing the port against upstream they are the questions worth asking.
Where libphonenumber-android is the wrong choice
Three cases, and the first one is about whether the problem still exists.
The reason this port exists is a performance characteristic of Android resource loading, and the linked blog post about it dates from 2016. Android has changed since then, in particular around how app bundles and the class loader behave. If the slow path has been addressed upstream or by the platform, this port buys you nothing and costs you a namespace migration. That is worth checking before you take on a fork, and the cheapest way to check is to measure the original library on a real device with your minimum supported Android version.
The second is anything outside Android. The API is Android shaped, with a createInstance method that takes a Context, and the classes live under io.michaelrocks.libphonenumber.android. If your server side or your iOS app needs the same parsing, you are not going to share a dependency here, and a platform neutral wrapper around the upstream library is the better investment for a codebase that spans platforms.
The third is the maintenance risk of a port. Upstream libphonenumber is a Google project with its own release cadence, and this port follows it, with a version number that tracks the upstream release. Every upstream upgrade is a port upgrade for you, and the README does not promise that every upstream change is mirrored. The update.sh script in the repository root is the visible mechanism for that, and a port that lags is a port carrying an older metadata set, and metadata age is exactly the failure mode you would not notice in a test.
Editorial conclusion
Adopt libphonenumber-android if your Android app needs phone number parsing, formatting and validation and you have measured the original library being slow on device, or you simply do not want to work around it. Do not adopt it if you are off Android, since it is an Android port with an Android shaped API, and note that it is not fully compatible with the upstream library. Before you adopt it, read the three API differences in the README, because PhoneNumberUtil no longer has a static getInstance and you have to hold an instance yourself, and check whether the upstream library has already addressed the resource loading problem for your minimum Android version.
Frequently asked questions
Why does libphonenumber-android exist?
Because Google's libphonenumber uses the Class.getResourceAsStream method internally, which is very slow on Android. The port's stated goal is to fix those two issues by loading the metadata in a way that works well on Android.
How do I add libphonenumber-android to an Android project?
Add mavenCentral() to your repositories and the dependency io.michaelrocks:libphonenumber-android:9.0.40 to your dependencies block. The sample/ directory in the repository contains a working build.gradle.kts and a proguard-rules.pro.
Is libphonenumber-android fully compatible with the original libphonenumber?
The README says it is not. The differences are that every class is repackaged to io.michaelrocks.libphonenumber.android, that PhoneNumberUtil no longer has a getInstance method so you must create an instance with one of the PhoneNumberUtil.createInstance methods and store it, and that a createInstance method taking a Context has been added as the default way to obtain an instance on Android.
What do I need to change if I obfuscate my app with DexGuard?
The README says you may need to add -keepresourcefiles assets/io/michaelrocks/libphonenumber/android/** to your DexGuard configuration, so the library's metadata assets survive obfuscation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/michaelrocks-libphonenumber-android)Was this useful?