Home Java Cleartext HTTP Blocked — Allow Host or Use HTTPS
Beginner 5 min · September 23, 2026

Cleartext HTTP Blocked — Allow Host or Use HTTPS

Android 9+ blocks http:// traffic with Cleartext HTTP not permitted.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 10 min
  • Basic Android manifest structure
  • HTTP versus HTTPS fundamentals
  • Running an emulator image
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 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
✦ Definition~90s read
What is Android Cleartext HTTP Fix?

Cleartext HTTP not permitted is Android's default refusal, since API 28, to open unencrypted http:// connections. The platform NetworkSecurityPolicy denies cleartext unless a network security config explicitly allows it, and the denial surfaces as java.io.IOException: Cleartext HTTP traffic to HOST not permitted at connect time.

Imagine sending company secrets on postcards instead of sealed envelopes.

HTTPS connections are unaffected; the policy targets unencrypted transport only.

The mechanism has three layers. The manifest's application tag may set usesCleartextTraffic or point at a network security config file. The config file under res/xml can set base-config defaults and per-domain overrides with cleartextTrafficPermitted flags.

The platform evaluates the target host against this policy before opening any socket — denied hosts fail before a single byte leaves the phone.

Beginners confuse this error with three lookalikes. A missing INTERNET permission throws SecurityException, not this IOException. A dead server throws ConnectException or SocketTimeoutException after attempting the connection. A TLS problem throws SSLHandshakeException after the handshake starts. Only the cleartext ban names the policy in the message and fails before connecting.

The intended direction is unambiguous: the platform wants HTTPS everywhere and offers exceptions only as migration tools. Treat every exception as debt with an owner and an expiry, and treat the default deny as the permanent state of the app. Apps that internalize that direction stop fighting the ban and start deleting http:// URLs.

Plain-English First

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.

ApiClient.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ApiClient {
    private final ExecutorService pool = Executors.newFixedThreadPool(2);

    public void fetchStatus() {
        pool.execute(() -> {
            HttpURLConnection c = null;
            try {
                URL url = new URL("https://api.example.com/status");
                c = (HttpURLConnection) url.openConnection();
                c.setConnectTimeout(8000);
                c.setReadTimeout(8000);
                int code = c.getResponseCode();
                System.out.println("status: " + code);
            } catch (IOException e) {
                System.out.println("request failed: " + e.getMessage());
            } finally {
                if (c != null) {
                    c.disconnect();
                }
            }
        });
    }
}
📊 Production Insight
A team chased a phantom server outage for 2 hours while dashboards stayed green — no request ever left the phones. Rule: the words Cleartext HTTP traffic not permitted end the server investigation instantly.
🎯 Key Takeaway
API 28+ refuses http:// with an IOException naming the host. Localhost is exempt, so dev hides the bug — read the message first when triaging.

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.

📊 Production Insight
An unscoped exception added for one host survived 3 years and covered 40 endpoints nobody audited. Rule: review network security configs quarterly and delete entries whose backends now serve TLS.
🎯 Key Takeaway
Permit cleartext per-domain for the one legacy host, keep the global default deny, and attach an owner plus expiry ticket to every entry.

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.

TlsCheck.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TlsCheck {
    public static void printTlsInfo(String httpsUrl) throws IOException {
        URL url = new URL(httpsUrl);
        HttpsURLConnection c = (HttpsURLConnection) url.openConnection();
        try {
            c.setConnectTimeout(8000);
            c.connect();
            System.out.println("cipher: " + c.getCipherSuite());
            System.out.println("cert: " + c.getServerCertificates()[0]);
        } finally {
            c.disconnect();
        }
    }
}
📊 Production Insight
A backend team enabled TLS but left an HTTPS-to-HTTP redirect that downgraded every call. Rule: curl -v the full chain after migration — the redirect direction matters as much as the certificate.
🎯 Key Takeaway
Free certificate, server redirect, https:// URLs, HSTS. Verify with curl first, then assert TLS programmatically in integration tests.

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.

⚠ App-Wide Cleartext Is Never the Answer
usesCleartextTraffic=true on the application tag downgrades every connection your app makes — APIs, analytics, ads, SDKs. One flag, total exposure.
📊 Production Insight
An inherited app-wide flag covered 40 endpoints for years until a security audit flagged it as a finding. Rule: grep manifests for usesCleartextTraffic in CI and fail the build on app-wide true.
🎯 Key Takeaway
App-wide cleartext exposes every endpoint to sniffing and injection and complicates store review. Remove it host by host until the default deny holds.

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.

SchemeGuard.javaJAVA
1
2
3
4
5
6
7
8
9
10
public class SchemeGuard {
    public static URL requireHttps(String raw) throws MalformedURLException {
        URL url = new URL(raw);
        if (!"https".equalsIgnoreCase(url.getProtocol())) {
            throw new IllegalArgumentException("cleartext URL rejected: " + raw);
        }
        return url;
    }
}
📊 Production Insight
A debug-only exception leaked into release through manifest merging and re-broke 20,000 users. Rule: inspect the merged release manifest in CI — source manifests lie by omission.
🎯 Key Takeaway
Reproduce on API 28+ with logcat, guard schemes in code, inspect the merged release manifest, and test the release artifact on modern images.

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.

EndpointAudit.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EndpointAudit {
    private static final List<String> BLOCKED = List.of("http://");

    public static void audit(String... urls) {
        for (String u : urls) {
            String lower = u.toLowerCase(Locale.US);
            for (String bad : BLOCKED) {
                if (lower.startsWith(bad) && !isLoopback(u)) {
                    throw new IllegalArgumentException("non-https endpoint: " + u);
                }
            }
        }
    }

    private static boolean isLoopback(String u) {
        return u.contains("localhost") || u.contains("10.0.2.2") || u.contains("127.0.0.1");
    }
}
📊 Production Insight
An SDK minor bump silently reintroduced an http:// endpoint 4 months after cleanup; the grep gate caught it in CI. Rule: audit SDK-owned endpoints on every dependency update, not just your own URLs.
🎯 Key Takeaway
Grep gates, manifest lint, modern-emulator release tests, and ticketed exception expiries keep cleartext out permanently.
● Production incidentPOST-MORTEMseverity: high

An http:// Backend Met the API 28 Ban and 68,000 Users Went Dark

Symptom
On Android 9 and newer, the app showed a generic network-error screen immediately after splash; retry never helped. Support logged 4,200 cannot-connect tickets in 5 hours. Backend dashboards stayed green — CPU, latency, and error rates normal — because no request ever arrived.
Assumption
The team tested the release on the office emulator image, which ran API 27 — one level below the ban. Their staging backend served HTTP because the TLS certificate had expired two years earlier and nobody renewed it since internal traffic worked. The release notes mentioned no network changes, so nobody connected the field failures to a platform security policy.
Root cause
The app's API base URL was hardcoded as http://api.example.com, and the backend served plain HTTP with an expired certificate nobody had renewed. Every device on API 28+ threw IOException: Cleartext HTTP traffic not permitted on launch, blocking login for 100% of users on modern phones — roughly 68,000 devices in the first 5 hours. Older phones kept working, which split the reports and delayed diagnosis by 2 hours while the team chased a phantom server outage.
Fix
The hotfix shipped a network security config scoped to the single API host, restoring service within 5 hours. Over the next 3 weeks the backend team provisioned a real certificate, added HTTP-to-HTTPS redirects, and the app migrated every URL to https://. The exception was deleted in the following release, and CI gained a release-on-API-34 install step plus a grep gate on http:// URLs.
Key lesson
  • 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.
Production debug guideFive checks that distinguish a dead server from a platform security ban.5 entries
Symptom · 01
Release build fails to reach the server on modern phones
Fix
Run adb logcat -s System.err AndroidRuntime and reproduce the failing screen. The line java.io.IOException: Cleartext HTTP traffic to HOST not permitted names the exact host and proves the ban — not a server outage — is the cause. Fix that host's URL or config.
Symptom · 02
Need a full inventory of cleartext URLs in the app
Fix
Run grep -rn "http://" app/src/main/java app/src/main/res and list every hit. Then run adb shell dumpsys package your.package | grep -i config to confirm the manifest state. Each http:// URL needs either an https:// replacement or a scoped exception.
Symptom · 03
Exception added but cleartext still fails
Fix
Run ./gradlew :app:mergeDebugResources --info and check the merged manifest at app/build/intermediates/merged_manifests. Confirm android:networkSecurityConfig points at a file that exists under res/xml. A dangling reference means your exception is silently ignored.
Symptom · 04
Unsure whether the backend even supports HTTPS
Fix
Run curl -v https://your-host/ from a terminal. If curl cannot complete a TLS handshake, the backend has no certificate and no app-side config will help. Fix the server first, then the app URL.
Symptom · 05
Debug works on the old emulator, release fails in the field
Fix
Run ./gradlew :app:installRelease on an API 34 emulator image and exercise every screen with adb logcat -s System.err running. Old API 27 emulators predate the ban and prove nothing — the release matrix must include API 28+.
Cleartext HTTP Failures Compared
Root CauseHow to ConfirmFixPrevention
http:// URL on API 28+ with default configIOException: Cleartext HTTP traffic to host not permitted in logcatSwitch the URL to https:// and serve TLS on the backendGrep the codebase for http:// URLs in CI and fail the build
Missing networkSecurityConfig referenceManifest lacks android:networkSecurityConfig; failure on all HTTP hostsAdd the config file under res/xml and reference it from the manifestVerify mergeDebugResources output includes the config
Overbroad usesCleartextTraffic=trueGrep finds the flag on the application tag covering every hostReplace with a domain-config scoped to the one legacy hostLint rule banning app-wide cleartext flags
Debug-only exception leaking into releaseRelease manifest or build type inherits a debug exception entryKeep exceptions in debug manifests only; release stays HTTPS-onlySeparate source sets: debug versus main manifests
Backend redirecting HTTPS back to HTTPHTTPS URL still fails; curl -v shows a 301 down to http://Fix the server to terminate TLS and never downgradeMonitor the resolved scheme in staging with curl checks
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
ApiClient.javapublic class ApiClient {The API 28 Cleartext Ban
TlsCheck.javapublic class TlsCheck {The Real Fix
SchemeGuard.javapublic class SchemeGuard {Confirming and Testing
EndpointAudit.javapublic class EndpointAudit {Locking It In

Key takeaways

1
API 28+ blocks cleartext HTTP by default
an http:// URL throws IOException naming the host.
2
Localhost is exempt and old emulators predate the ban, so dev hides the bug until release.
3
Scope any exception to the single legacy host; never set app-wide usesCleartextTraffic=true.
4
HTTPS is the real fix
free certificates, server redirect, and updated URLs.
5
Test release builds on API 28+ emulators in CI to reproduce field conditions.
6
Grep for http:// URLs in CI so new cleartext endpoints cannot sneak back in.

Common mistakes to avoid

5 patterns
×

Adding a cleartext exception instead of enabling HTTPS

Symptom
App works, but traffic is sniffable on public Wi-Fi, Play review flags the data-safety section, and a future auditor asks why a banking app allows HTTP. The exception becomes permanent because nobody schedules the HTTPS migration.
Fix
Serve everything over HTTPS and delete the exception. Get a free certificate, redirect HTTP to HTTPS server-side, and point the app at the https:// URL. The exception entry should live only in version control history, never in a shipped manifest.
×

Setting usesCleartextTraffic=true for the whole app

Symptom
Every host — analytics, ads, APIs — now allows HTTP, tripling the attack surface. One compromised Wi-Fi hotspot can strip or inject any of those calls, and the manifest gives no hint which host actually needed it.
Fix
Scope the exception to the exact legacy host with a domain-config entry, keep cleartextTrafficPermitted false everywhere else, and add a dated comment plus a ticket for migrating that host. Review the file quarterly.
×

Referencing a network security config file that doesn't exist

Symptom
Manifest points at @xml/network_security_config but the file was never created or lives in the wrong folder. The build may warn, the exception silently does nothing, and cleartext still fails at runtime.
Fix
Put the network_security_config.xml file in res/xml/, reference it from the application tag, and verify with ./gradlew :app:mergeDebugResources plus a monitoring grep. Treat a missing file as a build failure, not a silent default.
×

Testing only on API 27 and below where cleartext still works

Symptom
Debug builds on an old emulator work perfectly, then the release crashes on every modern phone. The Play pre-launch report on API 34 shows red while your desk device shows green.
Fix
Test on an API 28+ emulator or device with the exact release manifest. Add a CI step that installs the release build on an API 34 emulator and hits every endpoint. Emulator API level is a test-matrix item, not a detail.
×

Catching the IOException and assuming the server is down

Symptom
Crash logs blame the backend team, who prove their server is healthy. Days of cross-team argument end when someone reads the actual message: Cleartext HTTP traffic to host not permitted.
Fix
Catch IOException around the connection, check the scheme before opening it, and log the full URL (scheme plus host) in debug. Confirm failures with adb logcat -s System.err so you see Cleartext HTTP traffic not permitted instead of guessing.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What happens when an app opens an http:// URL on API 28+?
Q02SENIOR
How do you allow cleartext for one legacy host only?
Q03SENIOR
Why is usesCleartextTraffic=true risky?
Q04SENIOR
Why does cleartext work in dev but fail in production?
Q05SENIOR
Plan a full cleartext-to-HTTPS migration for a shipped app.
Q01 of 05JUNIOR

What happens when an app opens an http:// URL on API 28+?

ANSWER
Starting with API 28, Android blocks cleartext HTTP by default and throws IOException with Cleartext HTTP traffic not permitted. You confirm it in logcat by the exact message naming the host, and you fix it by switching to HTTPS or adding a scoped network security exception.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Does the ban affect WebViews and image loading too?
02
How long can a cleartext exception safely stay?
03
What happens if the config file has a typo in the domain?
04
Does localhost need an exception during development?
05
Will Play review reject apps with cleartext exceptions?
06
How do I migrate a legacy backend to HTTPS?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's Android. Mark it forged?

5 min read · try the examples if you haven't

Previous
Android NetworkOnMainThread Fix
2 / 3 · Android
Next
Android Insufficient Storage Install Fix