bumptech/glide · error · IOException

Stream is closed

Error message

Stream is closed

What it means

IOException thrown by RecyclableBufferedInputStream.reset() when buf==null, i.e. the stream has been closed. reset() refuses to operate on a released buffer because mark state is no longer valid.

Source

Thrown at library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java:341

      }
      if (localIn.available() == 0) {
        return byteCount - required;
      }
      offset += read;
    }
  }

  /**
   * Resets this stream to the last marked location.
   *
   * @throws IOException if this stream is closed, no mark has been put or the mark is no longer
   *     valid because more than {@code readlimit} bytes have been read since setting the mark.
   * @see #mark(int)
   */
  @Override
  public synchronized void reset() throws IOException {
    if (buf == null) {
      throw new IOException("Stream is closed");
    }
    if (-1 == markpos) {
      throw new InvalidMarkException(
          "Mark has been invalidated, pos: " + pos + " markLimit: " + marklimit);
    }
    pos = markpos;
  }

  /**
   * Skips {@code byteCount} bytes in this stream. Subsequent calls to {@link #read} will not return
   * these bytes unless {@link #reset} is used.
   *
   * @param byteCount the number of bytes to skip. This method does nothing and returns 0 if {@code
   *     byteCount} is less than zero.
   * @return the number of bytes actually skipped.
   * @throws IOException if this stream is closed or another IOException occurs.
   */
  @Override

View on GitHub (pinned to eb14a895d8)

Solutions

  1. Avoid reusing streams across decode attempts; open a fresh stream for each.
  2. In custom decoders, ensure mark/reset happen on the same open stream instance without intervening close().
  3. Do not return a closed stream from a DataFetcher.

Example fix

// before
is.mark(1024);
// ... other code calls is.close()
is.reset(); // throws
// after
is.mark(1024);
try { /* read */ } finally { /* keep stream open until reset done */ }
is.reset();
is.close();
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the stream is open before reset.
boolean isOpen(RecyclableBufferedInputStream is) {
  // no public isOpen; track close() yourself
  return !closedFlag;
}

Try / catch

try { is.reset(); }
catch (IOException e) {
  // stream closed between mark and reset: reopen and retry from scratch
}

Prevention

When it happens

Trigger: Calling reset() after close(), or after the buffer was released to the ArrayPool. Marks cannot be honored once the backing array is gone.

Common situations: Decoders that mark/reset on an InputStream that another component (or Glide itself) has closed between the mark and reset. Reusing a pooled stream across decode attempts.

Related errors


AI-assisted analysis of bumptech/glide@eb14a895d8 (2026-08-14). Data as JSON: /api/errors/3a952f101ab8c9d1. Report an issue: GitHub.