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 a

View on GitHub (pinned to dd430f7053)

Solutions

  1. 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
  2. For 416, reset any persisted position/length state for that resource and reopen from position 0 (or the new content length)
  3. Verify the request URL, headers and Range logic against a curl reproduction of the failing request
  4. 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

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


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