google/ExoPlayer · error · CronetDataSource.OpenException

ERROR_CODE_FAILED_RUNTIME_CHECK

ERROR_CODE_FAILED_RUNTIME_CHECK

Error message

HTTP request with non-empty body must set Content-Type

What it means

OpenException (ERROR_CODE_FAILED_RUNTIME_CHECK) thrown by CronetDataSource while building the request when dataSpec.httpBody is non-null but no Content-Type header is present (after merging default and per-request properties). Cronet requires a content type for bodies, so the library fails fast before starting the request rather than surfacing a confusing native error later.

Source

Thrown at extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java:827

            .setPriority(requestPriority)
            .allowDirectExecutor();

    // Set the headers.
    Map<String, String> requestHeaders = new HashMap<>();
    if (defaultRequestProperties != null) {
      requestHeaders.putAll(defaultRequestProperties.getSnapshot());
    }
    requestHeaders.putAll(requestProperties.getSnapshot());
    requestHeaders.putAll(dataSpec.httpRequestHeaders);

    for (Entry<String, String> headerEntry : requestHeaders.entrySet()) {
      String key = headerEntry.getKey();
      String value = headerEntry.getValue();
      requestBuilder.addHeader(key, value);
    }

    if (dataSpec.httpBody != null && !requestHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
      throw new OpenException(
          "HTTP request with non-empty body must set Content-Type",
          dataSpec,
          PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK,
          Status.IDLE);
    }

    @Nullable String rangeHeader = buildRangeRequestHeader(dataSpec.position, dataSpec.length);
    if (rangeHeader != null) {
      requestBuilder.addHeader(HttpHeaders.RANGE, rangeHeader);
    }
    if (userAgent != null) {
      requestBuilder.addHeader(HttpHeaders.USER_AGENT, userAgent);
    }
    // TODO: Uncomment when https://bugs.chromium.org/p/chromium/issues/detail?id=711810 is fixed
    // (adjusting the code as necessary).
    // Force identity encoding unless gzip is allowed.
    // if (!dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP)) {
    //   requestBuilder.addHeader("Accept-Encoding", "identity");

View on GitHub (pinned to dd430f7053)

Solutions

  1. Set Content-Type explicitly on the DataSpec: setHttpRequestHeaders(Map.of("Content-Type", "application/octet-stream"))
  2. Alternatively configure setDefaultRequestProperties(Pair.create("Content-Type", "application/octet-stream")) on the CronetDataSource factory/builder for all requests with bodies
  3. Check for the header before open(): if dataSpec.httpBody != null, ensure a Content-Type is merged in

Example fix

// before
DataSpec dataSpec = new DataSpec.Builder()
    .setUri(licenseUrl)
    .setHttpBody(jsonBody.getBytes(UTF_8))
    .build();
dataSource.open(dataSpec); // throws: no Content-Type

// after
DataSpec dataSpec = new DataSpec.Builder()
    .setUri(licenseUrl)
    .setHttpRequestHeaders(
        ImmutableMap.of("Content-Type", "application/json"))
    .setHttpBody(jsonBody.getBytes(UTF_8))
    .build();
dataSource.open(dataSpec);
Defensive patterns

Strategy: validation

Validate before calling

DataSpec spec = dataSpec;
if (spec.httpBody != null
    && !spec.httpRequestHeaders.containsKey("Content-Type")
    && !defaultRequestProperties.containsKey("Content-Type")) {
  spec = spec.buildUpon()
      .setHttpRequestHeaders(ImmutableMap.<String, String>builder()
          .putAll(spec.httpRequestHeaders)
          .put("Content-Type", "application/octet-stream")
          .buildOrThrow())
      .build();
}
dataSource.open(spec);

Try / catch

try {
  dataSource.open(dataSpec);
} catch (CronetDataSource.OpenException e) {
  if (e.errorCode == PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK
      && dataSpec.httpBody != null) {
    // add Content-Type and retry
    dataSource.open(dataSpec.buildUpon()
        .setHttpRequestHeaders(ImmutableMap.of("Content-Type", "application/octet-stream"))
        .build());
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: DataSpec.Builder().setHttpRequestHeaders(...) with a body via setHttpBody but no "Content-Type" entry; POST-style requests (e.g. DRM license requests or custom auth) routed through CronetDataSource with only default headers that omit Content-Type; assuming the datasource adds a default content type.

Common situations: License/token request POSTs with JSON bodies; migrating from DefaultHttpDataSource (which tolerated the omission) to CronetDataSource; per-request headers overriding/removing the default Content-Type.

Related errors


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