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 operation

View on GitHub (pinned to dd430f7053)

Solutions

  1. Serve the media over https:// — this is the correct production fix and the one the error URL recommends
  2. For debug builds only, set android:usesCleartextTraffic="true" in the manifest, or a network security config permitting cleartext for the specific dev domain
  3. Audit playlist/manifest segment URIs and redirects to ensure none downgrade to http
  4. 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

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


AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14). Data as JSON: /api/errors/04942092a7a14c72. Report an issue: GitHub.