Debugging log · Networking
"Cleartext HTTP traffic not permitted"
I pointed an app at a local test server, ran it, and every request failed instantly — before it even left the device. The URL was fine, the server was up, and the same address worked in a browser. The difference was one letter: my URL started with http://, and modern Android refuses to send that by default.
The short version: since API 28, Android blocks unencrypted HTTP unless you explicitly allow it. Use https:// wherever you can. If you truly need plain HTTP — a local dev server, for instance — allow it for that host only via a network security config, not globally.
The error
java.io.IOException: Cleartext HTTP traffic to 192.168.0.10 not permitted
"Cleartext" just means unencrypted — plain HTTP rather than HTTPS. The request never reaches the network; the platform stops it on the device.
Why Android blocks it
Anything sent over plain HTTP travels readable by every device between the phone and the server — the coffee-shop Wi-Fi, the ISP, anyone running a proxy in between. They can read it and, worse, modify it in flight. For years this was opt-out; from Android 9 (API 28) the default flipped to blocked, so apps have to consciously say "yes, I really want to send this in the clear". The error is the platform protecting your users from a mistake, which is why the fix should be narrow rather than sweeping.
Fix 1 — use HTTPS (the real answer)
If the server supports TLS, just switch the URL to https:// and the error is gone with nothing else to configure. For any production endpoint this is the only correct fix. If your own backend doesn't have a certificate yet, that's the bug to solve — certificates are free and automatic these days, and every third-party API you'd integrate already requires HTTPS anyway.
Fix 2 — allow cleartext for one host (dev servers)
Sometimes you genuinely need HTTP: a machine on your LAN, an emulator hitting 10.0.2.2, a legacy device you can't put TLS on. Allow it for that host only. Create res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.0.10</domain>
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>
Then reference it from the manifest:
<application
android:networkSecurityConfig="@xml/network_security_config"
... >
Every other domain stays protected. That's the important part — you've made an exception, not a hole.
Better: only in debug builds
Put the config in your debug source set (src/debug/res/xml/) so the exception physically cannot ship in the release APK. Your dev server works, and production keeps the strict default with no chance of a stray flag escaping.
Don't reach for android:usesCleartextTraffic="true". It's the most-copied answer online and it disables the protection for every host in your app, in every build, forever. It makes the error go away and leaves your users' traffic readable on any network they join. Scope the exception instead.
Notes that save time
- Emulator to your machine: use
10.0.2.2, notlocalhost— inside the emulator, localhost is the emulator itself. - Physical device on Wi-Fi: use your computer's LAN IP, and make sure both are on the same network.
- Config changes need a rebuild — this is manifest/resource level, so a hot reload won't pick it up.
- Check the actual URL in a log before assuming the config is broken; a stray
http://in one constant is often the whole story.
IP addresses above are examples — substitute your own dev server's address.