didi/DoKit · error · IOException
Failed to decode stream.
Error message
Failed to decode stream.
What it means
BitmapHunter.decodeStream wraps BitmapFactory.decodeStream: when the factory returns null (it reports decode failures as null rather than throwing), the hunter converts it into IOException('Failed to decode stream.') so the request is treated as a retrievable/retryable IO failure. Null comes from data that is not a decodable image, a truncated stream, or an in-sample/decode-options mismatch. Because it is an IOException, Picasso routes it to the error path (error drawable / listener) rather than crashing the app.
Source
Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java:145
byte[] bytes = Utils.toByteArray(stream);
if (calculateSize) {
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
}
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
} else {
if (calculateSize) {
BitmapFactory.decodeStream(stream, null, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
markStream.reset(mark);
}
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
if (bitmap == null) {
// Treat null as an IO exception, we will eventually retry.
throw new IOException("Failed to decode stream.");
}
return bitmap;
}
}
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {View on GitHub (pinned to 626827cddb)
Solutions
- Pull the URL separately (OkHttp/curl) and confirm the bytes are actually a decodable image (file type, not an HTML error page)
- Attach an error fallback so the UI degrades gracefully: .error(R.drawable.broken).into(view)
- For custom sources, return a fresh, mark-supported stream from your RequestHandler (use Utils.readFileToBuffer-style buffering or ByteArrayInputStream)
- Re-encode problematic assets (CMYK JPEG -> RGB PNG) or downscale server-side
- If intermittent, rely on Picasso's retry semantics or add .memoryPolicy(MemoryPolicy.NO_CACHE) once to force a clean fetch
Example fix
// before
DokitPicasso.with(context).load(url).into avatarView;
// IOException: Failed to decode stream. -> blank view
// after
DokitPicasso.with(context)
.load(url)
.error(R.drawable.avatar_placeholder)
.into(avatarView, new com.squareup.picasso.Callback.EmptyCallback() {}); Defensive patterns
Strategy: fallback
Validate before calling
// Pre-flight check when the bytes come from your own source (optional):
static boolean isDecodable(byte[] bytes) {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, o);
return o.outWidth > 0 && o.outHeight > 0;
} Try / catch
// It is an IOException routed to the error path — handle via callback + error drawable:
picasso.load(url)
.error(R.drawable.broken)
.into(view, new Callback() {
@Override public void onSuccess() {}
@Override public void onError(Exception e) {
if (e instanceof IOException && "Failed to decode stream.".equals(e.getMessage())) {
reportBadImage(url); // e.g. fetch and inspect bytes server-side
}
}
}); Prevention
- Serve correctly-typed images from your CDN; never HTML error pages with 200 status
- Re-encode exotic formats (CMYK JPEG, exotic WebP) to RGB JPEG/PNG
- Return fresh mark-supported streams from custom RequestHandlers
- Always set .error(drawable) so decode failures degrade gracefully
When it happens
Trigger: Server returns 200 with HTML/error page bytes for an image URL; progressive JPEG or WebP variant unsupported by BitmapFactory on the API level; stream consumed twice without the mark/reset path (custom RequestHandler returning an already-read stream); resized decode where calculateInSampleSize produced invalid options.
Common situations: CDN serving an error page or truncated image on flaky networks; CMYK JPEGs; images with unusual EXIF; OOM-adjacent very large images where decode silently returns null.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unable to mark: <e>
- Cannot reset
- Mark not supported
- Unrecognized type of request: " + request
- Transformation " + transformation.key() + " crashed with exc
AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14).
Data as JSON: /api/errors/921c88dd043b27b8.
Report an issue: GitHub.