{"record":{"id":"0b2a27260073ba80","repo":"google/ExoPlayer","slug":"passed-buffer-is-not-a-direct-bytebuffer","errorCode":null,"errorMessage":"Passed buffer is not a direct ByteBuffer","messagePattern":"Passed buffer is not a direct ByteBuffer","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java","lineNumber":734,"sourceCode":"   * because the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is\n   * returned. Otherwise, the call will block until at least one byte of data has been read and the\n   * number of bytes read is returned.\n   *\n   * <p>Passed buffer must be direct ByteBuffer. If you have a non-direct ByteBuffer, consider the\n   * alternative read method with its backed array.\n   *\n   * @param buffer The ByteBuffer into which the read data should be stored. Must be a direct\n   *     ByteBuffer.\n   * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is available\n   *     because the end of the opened range has been reached.\n   * @throws HttpDataSourceException If an error occurs reading from the source.\n   * @throws IllegalArgumentException If {@code buffer} is not a direct ByteBuffer.\n   */\n  public int read(ByteBuffer buffer) throws HttpDataSourceException {\n    Assertions.checkState(opened);\n\n    if (!buffer.isDirect()) {\n      throw new IllegalArgumentException(\"Passed buffer is not a direct ByteBuffer\");\n    }\n    if (!buffer.hasRemaining()) {\n      return 0;\n    } else if (bytesRemaining == 0) {\n      return C.RESULT_END_OF_INPUT;\n    }\n    int readLength = buffer.remaining();\n\n    if (readBuffer != null) {\n      // If there is existing data in the readBuffer, read as much as possible. Return if any read.\n      int copyBytes = copyByteBuffer(/* src= */ readBuffer, /* dst= */ buffer);\n      if (copyBytes != 0) {\n        if (bytesRemaining != C.LENGTH_UNSET) {\n          bytesRemaining -= copyBytes;\n        }\n        bytesTransferred(copyBytes);\n        return copyBytes;\n      }","sourceCodeStart":716,"sourceCodeEnd":752,"githubUrl":"https://github.com/google/ExoPlayer/blob/dd430f7053a1a3958deea3ead6a0565150c06bfc/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java#L716-L752","documentation":"IllegalArgumentException thrown by CronetDataSource.read(ByteBuffer) when the supplied buffer is not a direct (off-heap) ByteBuffer. Cronet writes response bytes natively into the buffer, which requires native-addressable memory; heap ByteBuffers have no native pointer, so the API rejects them up front (documented on the method).","triggerScenarios":"Calling dataSource.read(ByteBuffer.allocate(n)) or wrapping a byte[] (both heap buffers) with the CronetDataSource ByteBuffer overload; adapting a DataSource into a NIO-based consumer that allocates heap buffers; test code reusing standard buffers.","commonSituations":"Wrapping CronetDataSource in custom download/cache code that uses ByteBuffer.allocate; porting code from a DataSource.read(byte[],...) loop into the ByteBuffer API without switching allocation.","solutions":["Allocate with ByteBuffer.allocateDirect(capacity) for every buffer passed to read(ByteBuffer)","Reuse a single direct buffer and flip/clear it between reads to avoid repeated native allocations","If you need byte[], use DataSource.read(byte[], int, int) instead, which copies internally","Wrap the call: assert buffer.isDirect() in debug builds to catch mistakes early"],"exampleFix":"// before\nByteBuffer buffer = ByteBuffer.allocate(64 * 1024);\nint read = dataSource.read(buffer); // throws IllegalArgumentException\n\n// after\nByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024);\nint read = dataSource.read(buffer);","handlingStrategy":"type-guard","validationCode":"ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024);\n// direct by construction; keep one buffer and flip()/clear() between reads","typeGuard":"static boolean isUsableByCronet(ByteBuffer buffer) {\n  return buffer != null && buffer.isDirect() && buffer.hasRemaining();\n}\n\n// usage\nif (!isUsableByCronet(buf)) {\n  ByteBuffer direct = ByteBuffer.allocateDirect(buf.capacity());\n  direct.put(buf).flip();\n  buf = direct; // converted heap -> direct\n}","tryCatchPattern":"try {\n  int read = dataSource.read(buffer);\n} catch (IllegalArgumentException e) {\n  if (buffer.isDirect()) throw e; // different IAE\n  buffer = ByteBuffer.allocateDirect(buffer.capacity()); // fix and retry\n  int read = dataSource.read(buffer);\n}","preventionTips":["Always allocate buffers for CronetDataSource.read(ByteBuffer) with allocateDirect","Prefer DataSource.read(byte[], int, int) when you ultimately need byte[] anyway","Assert buffer.isDirect() in debug builds to catch accidental heap buffers early"],"tags":["android","cronet","nio","api-misuse"],"backgroundTag":null,"analyzedSha":"dd430f7053a1a3958deea3ead6a0565150c06bfc","analyzedAt":"2026-08-14T12:22:02.982Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}