google/ExoPlayer · error · HttpDataSource.CleartextNotPermittedException
ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED
ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED
Error message
Cleartext HTTP traffic not permitted. See https://developer.android.com/guide/topics/media/issues/cleartext-not-permitted
What it means
CleartextNotPermittedException (PlaybackException error code ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED) thrown by CronetDataSource when the connection failure message contains 'err_cleartext_not_permitted'. Since Android 9 (API 28), plain http:// requests are blocked by default unless the app opts in, so any http data source open fails with this error.
Source
Thrown at extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java:557
currentUrlRequest = urlRequest;
} catch (IOException e) {
if (e instanceof HttpDataSourceException) {
throw (HttpDataSourceException) e;
} else {
throw new OpenException(
e, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, Status.IDLE);
}
}
urlRequest.start();
transferInitializing(dataSpec);
try {
boolean connectionOpened = blockUntilConnectTimeout();
@Nullable IOException connectionOpenException = exception;
if (connectionOpenException != null) {
@Nullable String message = connectionOpenException.getMessage();
if (message != null && Ascii.toLowerCase(message).contains("err_cleartext_not_permitted")) {
throw new CleartextNotPermittedException(connectionOpenException, dataSpec);
}
throw new OpenException(
connectionOpenException,
dataSpec,
PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
getStatus(urlRequest));
} else if (!connectionOpened) {
// The timeout was reached before the connection was opened.
throw new OpenException(
new SocketTimeoutException(),
dataSpec,
PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
getStatus(urlRequest));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// An interruption means the operation is being cancelled, in which case this exception should
// not cause the player to fail. If it does, it likely means that the owner of the operationView on GitHub (pinned to dd430f7053)
Solutions
- Serve the media over https:// — this is the correct production fix and the one the error URL recommends
- For debug builds only, set android:usesCleartextTraffic="true" in the manifest, or a network security config permitting cleartext for the specific dev domain
- Audit playlist/manifest segment URIs and redirects to ensure none downgrade to http
- Pre-check the scheme: refuse or rewrite http:// DataSpecs before calling dataSource.open()
Example fix
<!-- before: no opt-in, http blocked on API 28+ -->
<application ...>
<!-- after (debug only): targeted network security config -->
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">dev.example.com</domain>
</domain-config>
</network-security-config>
<!-- AndroidManifest.xml -->
<application android:networkSecurityConfig="@xml/network_security_config" ...> Defensive patterns
Strategy: fallback
Validate before calling
Uri uri = Uri.parse(dataSpec.uri);
if ("http".equals(uri.getScheme())
&& Build.VERSION.SDK_INT >= 28
&& !NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(uri.getHost())) {
// rewrite to https or fail with a clear message BEFORE dataSource.open()
uri = uri.buildUpon().scheme("https").build();
} Try / catch
try {
dataSource.open(dataSpec);
} catch (CleartextNotPermittedException e) {
// the URL is plain http and Android blocks it: switch origin to https and retry
DataSpec httpsSpec = dataSpec.buildUpon()
.setUri(dataSpec.uri.replaceFirst("http://", "https://"))
.build();
dataSource.open(httpsSpec);
} Prevention
- Serve all media over https in production; test servers get a debug-only network security config
- Audit playlists and redirect chains for http:// downgrades
- Check NetworkSecurityPolicy.isCleartextTrafficPermitted(host) before opening http data sources
When it happens
Trigger: Opening a DataSpec with an http:// (non-TLS) URL through CronetDataSource on API 28+ without a cleartext opt-in; a redirect from https:// to an http:// host; playlists whose segment URIs are absolute http:// while the manifest was https://.
Common situations: Test/dev servers on plain http; content from CDNs that serve http segments; app targetSdk >= 28 where android:usesCleartextTraffic defaults to false.
Related errors
- ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE
- ERROR_CODE_IO_BAD_HTTP_STATUS
- ERROR_CODE_FAILED_RUNTIME_CHECK
- Passed buffer is not a direct ByteBuffer
- No license URL
AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14).
Data as JSON: /api/errors/04942092a7a14c72.
Report an issue: GitHub.