Cleartext HTTP Blocked — Allow Host or Use HTTPS
Android 9+ blocks http:// traffic with Cleartext HTTP not permitted.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Basic Android manifest structure
- ✓HTTP versus HTTPS fundamentals
- ✓Running an emulator image
- Since API 28, Android throws IOException (Cleartext HTTP traffic not permitted) for any http:// URL unless explicitly allowed
- Localhost is exempt, so dev servers keep working — the ban only bites on real hosts, which is why it hides until release
- Quick fix: a networkSecurityConfig file scoped to the single legacy host, referenced from the manifest application tag
- Real fix: serve HTTPS with a valid certificate and change every URL to https:// — exceptions are technical debt
- Never set app-wide usesCleartextTraffic=true; it exposes analytics, ads, and APIs alike to sniffing
Imagine sending company secrets on postcards instead of sealed envelopes. Anyone handling the mail can read them. For years Android delivered your postcards without complaint. Since Android 9, the mailroom refuses postcards outright — every letter must be in a sealed envelope (HTTPS). Your app keeps handing over postcards (http:// URLs) and is surprised when they come back stamped refused. The fix is not arguing with the mailroom; it is buying envelopes.
You ship the release, and within an hour the tickets arrive: the app cannot reach the server. Nothing changed in your code. The backend team swears their API is healthy. Then someone pastes the actual logcat line — java.io.IOException: Cleartext HTTP traffic to app.example.com not permitted — and the room goes quiet.
Since API 28 (Android 9), the platform blocks unencrypted HTTP traffic by default. Any http:// URL your app opens throws an IOException instead of connecting. It is a security upgrade with a compatibility cliff: apps that worked for years suddenly fail on modern phones while working fine on the old emulator on your desk.
The desk-versus-field gap is what makes this error expensive. Developers test against local servers where localhost is exempt, or on old emulator images where the ban never fires. The crash appears only in release builds on current devices — exactly where it costs the most.
This article covers the ban, the two fixes, and their very different price tags. The quick fix is a network security config exception for a legacy host. The real fix is HTTPS everywhere. You will learn both, when each is justified, and how to prove no cleartext remains before you ship.
The API 28 Cleartext Ban: What Throws and Why
The cleartext ban lives in the platform network stack: starting with API 28, the default NetworkSecurityPolicy refuses cleartext traffic and any http:// connection throws java.io.IOException with the message Cleartext HTTP traffic to HOST not permitted. It is not a warning and not a StrictMode penalty — the connection never opens, on every device running Android 9 or newer.
Two exemptions explain why developers are always surprised. First, localhost and the emulator loopback addresses are exempt, so http://10.0.2.2 during development works forever. Second, apps targeting older SDKs on older devices predate the ban entirely. Your desk setup combines both exemptions; your users combine neither.
The exception message is unusually helpful: it names the exact host that was refused. When triaging, read the message before anything else. If it says Cleartext HTTP traffic not permitted, stop investigating the server — the request never left the phone, and every server-side dashboard will look green while users see errors.
Note the failure mode is fail-closed. There is no partial connection, no fallback, no retry that helps. That harshness is deliberate: silent cleartext would leak credentials on hostile networks, so the platform refuses rather than warns. Your job is to give it nothing cleartext to refuse.
The Scoped Exception: networkSecurityConfig Done Right
When a legacy backend cannot move to HTTPS today, the platform offers a scoped escape hatch: a network security config XML file that permits cleartext for named domains while the default deny stays in force everywhere else. This is a bandage with an expiry date, not a design.
The file lives at res/xml/network_security_config.xml and is referenced from the manifest's application tag through android:networkSecurityConfig. Inside, a base-config denies cleartext globally while a domain-config entry names the legacy host and permits it, with includeSubdomains set only if the subdomains genuinely share the legacy backend.
Scope is the entire point. Permitting one host keeps analytics, crash reporting, and ad SDKs on encrypted transport while the single legacy integration keeps working. An app-wide usesCleartextTraffic flag cannot say that — it downgrades every connection the app makes, including ones you never audited.
Every exception entry must carry a dated comment naming the owner and the migration ticket. Without that, the bandage fossilizes: two years later nobody remembers why it exists and nobody dares remove it. Review the file quarterly and delete entries the moment their backend serves TLS.
The Real Fix: HTTPS Everywhere, Verified End to End
The real fix removes cleartext instead of permitting it: provision a certificate for the backend, redirect HTTP to HTTPS server-side, and point the app at https:// URLs. Certificates are free, the server change is a handful of lines, and the app change is a URL string. Nothing about this fix is exotic — which is why lingering HTTP backends are a process failure, not a technical one.
Verify the server before touching the app. A terminal curl -v against the https:// URL shows the handshake, the certificate chain, and any downgrade redirect in seconds. If curl cannot complete TLS, no manifest entry will save you — fix the server first, then update the client.
In the app, HttpsURLConnection works transparently once the URL scheme is https. You can inspect the negotiated cipher and the server certificate programmatically, which is useful for debug screens and integration tests that assert TLS is actually in use rather than assuming it.
Finish with defense in depth: HSTS headers tell clients to never attempt HTTP again, and server-side redirects catch stale http:// URLs from old app versions still in the field. Old versions keep working through the redirect while new versions never emit cleartext at all.
usesCleartextTraffic Risks: Why App-Wide Flags Are Dangerous
The usesCleartextTraffic attribute on the manifest's application tag is the permitAll() of networking: one line that silences the error by deleting the protection. Setting it to true reverts the entire app to pre-API-28 behavior, where every http:// URL — including third-party SDK endpoints you never reviewed — transmits in the open.
The threat is concrete, not theoretical. On public Wi-Fi, cleartext traffic can be sniffed for tokens and injected with malicious responses. A man-in-the-middle that downgrades or rewrites an analytics response can poison business data; one that rewrites an API response can drive app behavior. TLS exists precisely because the network between phone and server is hostile.
There is also a store-policy cost. Data-safety declarations must describe transmission honestly, and unencrypted personal data invites extra scrutiny or rejection. An app-wide flag is difficult to explain to reviewers and impossible to scope in answers.
If you inherit this flag, treat its removal as a security ticket with a severity, not a cleanup chore. Inventory every http:// URL, migrate or scope-exception each one, flip the flag back to false, and verify with a release build on an API 34 emulator. The diff is small; the exposure it closes is not.
Confirming and Testing: From logcat to Release Matrix
Confirming the ban takes two commands and one emulator. First reproduce on an API 28+ image with adb logcat -s System.err running — the Cleartext HTTP traffic not permitted line names the host and closes the case. Then inventory every http:// string with a codebase grep so the fix covers SDK endpoints and image URLs, not just your own API client.
A small scheme guard in the networking layer turns future regressions into loud failures. Requiring https at URL-construction time means a pasted http:// URL crashes in development with a clear message instead of shipping to users. It costs five lines and catches the exact mistake code review misses.
Check the merged manifest, not just the source manifest. Build flavors and library manifests merge, and a debug exception can leak into release through source-set inheritance. Open the merged manifest under build/intermediates and confirm the release variant carries exactly the config you intend.
Finally, test the release artifact — not debug — on the newest emulator image you support. Debug and release manifests differ, ProGuard and resource shrinking differ, and only the release build on a modern API level reproduces field conditions. That one install catches the whole class of it-works-on-my-machine cleartext surprises.
Locking It In: CI Gates and Endpoint Hygiene
Preventing recurrence is a CI problem, not a memory problem. A grep gate that fails the build on new http:// strings (excluding loopback dev addresses) stops the next developer from pasting a cleartext URL. A manifest lint check rejects app-wide cleartext flags. A release-on-modern-emulator install step reproduces the ban before users do.
Keep a living inventory of every endpoint the app calls, including SDK-owned ones. SDK updates occasionally change endpoint schemes, and a minor version bump can reintroduce cleartext months after you cleaned it. The audit helper above encodes the rule — https or loopback, nothing else — so endpoint lists can be asserted in unit tests.
Schedule backend TLS follow-ups like any other debt. Each scoped exception owns a ticket, an owner, and a quarter. When the backend serves TLS, delete the exception, update the URL, and watch packet captures confirm zero port-80 traffic from the app.
The end state is boring on purpose: every URL https, the manifest carrying no exceptions, CI enforcing both, and pre-launch reports green on API 34. Boring is the goal — it means the mailroom never sees another postcard. Measure the migration with a release-build packet capture: zero port-80 requests from the app is the only proof that counts.
An http:// Backend Met the API 28 Ban and 68,000 Users Went Dark
- Your test matrix must include the platform behavior changes of every API level you ship on — one stale emulator image hid a total outage from the whole team.
- An expired certificate is a production incident waiting to happen; monitor TLS expiry on every host the app calls, including internal ones.
- Temporary security exceptions need expiry tickets with owners and dates, or temporary becomes permanent by default.
| File | Command / Code | Purpose |
|---|---|---|
| ApiClient.java | public class ApiClient { | The API 28 Cleartext Ban |
| TlsCheck.java | public class TlsCheck { | The Real Fix |
| SchemeGuard.java | public class SchemeGuard { | Confirming and Testing |
| EndpointAudit.java | public class EndpointAudit { | Locking It In |
Key takeaways
Common mistakes to avoid
5 patternsAdding a cleartext exception instead of enabling HTTPS
Setting usesCleartextTraffic=true for the whole app
Referencing a network security config file that doesn't exist
Testing only on API 27 and below where cleartext still works
Catching the IOException and assuming the server is down
Interview Questions on This Topic
What happens when an app opens an http:// URL on API 28+?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Android. Mark it forged?
5 min read · try the examples if you haven't