What happened
Switched a staging API from HTTPS to plain HTTP temporarily, app started crashing on Android. Logcat showed:
java.io.IOException: Cleartext HTTP traffic to api.staging.example.com not permitted
Android 9 (API 28) made this a hard block โ no warnings, just a crash. Before Pie, HTTP worked fine. Starting from API 28, any http:// request to a domain not explicitly allowed gets killed by the OS. There's no runtime workaround.
Three scenarios where this hits hardest: a backend that's temporarily HTTP-only during migration, a local dev server at 192.168.x.x, or a third-party SDK still phoning home over HTTP. The last one is the sneaky one โ you didn't write that code.
Debug process
Pin down the exact domain triggering the block first. Pull full logcat and filter for CLEARTEXT:
adb logcat | grep -i cleartext
You'll see something like:
W/NetworkSecurityConfig: Cleartext HTTP traffic to api.staging.example.com not permitted
That's your target domain. Sometimes it's not your code at all โ embedded analytics SDKs like Firebase Crashlytics older versions or ad SDKs are common culprits.
Next, check whether a network_security_config.xml already exists:
find app/src/main/res -name "network_security_config.xml"
If it's already there, someone set up network security rules and your domain just isn't in the list.
Solution
Option 1: Allow specific domains (recommended)
Create app/src/main/res/xml/network_security_config.xml if it doesn't exist:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">api.staging.example.com</domain>
<domain includeSubdomains="true">192.168.1.100</domain>
</domain-config>
</network-security-config>
Register it in AndroidManifest.xml inside the <application> tag:
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
Surgical and auditable. Production HTTPS traffic stays fully protected โ only the domains you list get HTTP access.
Option 2: Allow HTTP for debug builds only
HTTP only needed in development? Scope the permissive config to debug builds. Create app/src/debug/res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
Add the android:networkSecurityConfig attribute only in the debug manifest (app/src/debug/AndroidManifest.xml). Release builds never see it.
Option 3: Nuclear option (not for production)
Drop this directly into AndroidManifest.xml:
<application
android:usesCleartextTraffic="true"
...>
Allows HTTP everywhere. Never ship this โ Google Play will flag it, and you're removing transport security for every user. Use it only to confirm HTTP is actually the problem, then remove it.
Option 4: Switch to HTTPS (the real fix)
For servers you control, force HTTPS. For local dev, grab a self-signed cert or run ngrok to get a proper HTTPS tunnel in under a minute. For third-party SDKs still on HTTP, file a bug with the vendor โ that's on them.
Verification
Rebuild and install:
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
Trigger the network call in the app. The Cleartext HTTP traffic warning should be gone from logcat. If you're using OkHttp, add a logging interceptor to see the full request complete:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BASIC))
.build();
A 200 response confirms the fix. An IOException means the domain still isn't whitelisted.
Tips
Can't figure out which library is making the HTTP call? Enable StrictMode in your Application class during debug:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectNetwork()
.penaltyLog()
.build());
This logs the full stack trace โ right down to the class that initiated the request. Much faster than grepping through 15 SDKs manually.
Working with IP ranges for local dev servers or corporate proxies? The Subnet Calculator on ToolCraft makes it quick to check CIDR ranges. Handy when you're unsure whether 192.168.x.x and 10.x.x.x fall under the same subnet before writing your domain-config rules.
Lessons learned
- Keep
android:usesCleartextTraffic="true"out of the main manifest entirely. Debug-only scope if you absolutely need it. - Per-domain config in
network_security_config.xmlis the right tool โ explicit, reviewable in code, and invisible to production traffic. - Run
adb logcat | grep -i cleartextbefore every release. Third-party SDKs are a silent source of this error that often gets missed until someone reviews the Play Store security report. - Android's Network Security Config docs are unusually good. A full read covers certificate pinning, trust anchors, and debug overrides โ all the options you'll eventually need.

