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
- Set Content-Type explicitly on the DataSpec: setHttpRequestHeaders(Map.of("Content-Type", "application/octet-stream"))
- Alternatively configure setDefaultRequestProperties(Pair.create("Content-Type", "application/octet-stream")) on the CronetDataSource factory/builder for all requests with bodies
- 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
- Any DataSpec with setHttpBody must also set a Content-Type header — pair them always
- Set a default Content-Type via setDefaultRequestProperties for body-carrying pipelines (DRM, auth)
- Unit-test request building paths that add bodies so the header is never forgotten
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
- ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED
- ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE
- Unexpected color filter ${colorFilterSelection}
- The item must specify its mimeType
- ERROR_CODE_IO_BAD_HTTP_STATUS
AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14).
Data as JSON: /api/errors/ed1fb5899a451a2f.
Report an issue: GitHub.