google/ExoPlayer · error · HttpDataSource.InvalidResponseCodeException
ERROR_CODE_IO_BAD_HTTP_STATUS
ERROR_CODE_IO_BAD_HTTP_STATUS
Error message
Response code: ${responseCode} What it means
InvalidResponseCodeException (ERROR_CODE_IO_BAD_HTTP_STATUS) thrown by CronetDataSource when the server answered with a non-2xx/3xx response code. For 416 the exception carries a DataSourceException(ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE) cause, mapping the classic 'range not satisfiable' case to a distinct playback error code.
Source
Thrown at extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java:611
opened = true;
transferStarted(dataSpec);
return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0;
}
}
byte[] responseBody;
try {
responseBody = readResponseBody();
} catch (IOException e) {
responseBody = Util.EMPTY_BYTE_ARRAY;
}
@Nullable
IOException cause =
responseCode == 416
? new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE)
: null;
throw new InvalidResponseCodeException(
responseCode,
responseInfo.getHttpStatusText(),
cause,
responseHeaders,
dataSpec,
responseBody);
}
// Check for a valid content type.
Predicate<String> contentTypePredicate = this.contentTypePredicate;
if (contentTypePredicate != null) {
@Nullable String contentType = getFirstHeader(responseHeaders, HttpHeaders.CONTENT_TYPE);
if (contentType != null && !contentTypePredicate.apply(contentType)) {
throw new InvalidContentTypeException(contentType, dataSpec);
}
}
// If we requested a range starting from a non-zero position and received a 200 rather than aView on GitHub (pinned to dd430f7053)
Solutions
- Inspect responseCode: 403/401 means refresh the signed URL or auth header and retry with a new DataSpec; 404 means the media moved or expired; 416 means the requested range is beyond the current resource length
- For 416, reset any persisted position/length state for that resource and reopen from position 0 (or the new content length)
- Verify the request URL, headers and Range logic against a curl reproduction of the failing request
- Handle InvalidResponseCodeException in a custom HttpDataSource.ResponseValidator / player error callback to refresh credentials and recover instead of failing playback
Example fix
// before: no recovery on HTTP error
player.prepare(new MediaItem.Builder().setUri(url).build());
// after: react to 416 by reopening from zero
player.addListener(new Player.Listener() {
@Override
public void onPlayerError(PlaybackException error) {
if (error.errorCode == PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE) {
player.seekTo(0); // stale range; restart the resource
player.prepare();
}
}
}); Defensive patterns
Strategy: try-catch
Validate before calling
// HEAD-check before streaming (optional, costs one round trip)
HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
c.setRequestMethod("HEAD");
int code = c.getResponseCode();
if (code == 403) refreshSignedUrl();
else if (code == 404) removeDeadItemFromQueue();
else if (code == 416) resetPersistedPosition(url); Try / catch
try {
dataSource.open(dataSpec);
} catch (InvalidResponseCodeException e) {
switch (e.responseCode) {
case 401: case 403: refreshCredentialsAndRetry(); break; // re-sign URL, rebuild DataSpec
case 404: skipToNextItem(); break;
case 416: dataSource.open(dataSpec.buildUpon().setPosition(0).setLength(C.LENGTH_UNSET).build()); break;
default: throw e;
}
} Prevention
- Refresh signed URLs/tokens before they expire rather than after a 403
- On 416, reset stored positions/lengths for the resource instead of resuming the stale range
- Reproduce failing requests with curl -H 'Range: ...' to confirm server-side range behavior
When it happens
Trigger: Server returns 403 (expired signed URL / hotlink protection), 404 (moved media), 416 (range beyond EOF, typically after stale cached positions or an incorrect DataSpec.length), or 5xx; expired auth tokens in request headers.
Common situations: Signed CDN URLs past their expiry; live stream segment removed after sliding window; resuming a download at a position the current file no longer supports; DRM/token middleware rejecting requests.
Related errors
- ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED
- ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE
- Passed buffer is not a direct ByteBuffer
- ERROR_CODE_FAILED_RUNTIME_CHECK
- No license URL
AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14).
Data as JSON: /api/errors/2be2205211ab8adf.
Report an issue: GitHub.