{"record":{"id":"921c88dd043b27b8","repo":"didi/DoKit","slug":"failed-to-decode-stream","errorCode":null,"errorMessage":"Failed to decode stream.","messagePattern":"Failed to decode stream\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java","lineNumber":145,"sourceCode":"      byte[] bytes = Utils.toByteArray(stream);\n      if (calculateSize) {\n        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n        RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,\n            request);\n      }\n      return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);\n    } else {\n      if (calculateSize) {\n        BitmapFactory.decodeStream(stream, null, options);\n        RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,\n            request);\n\n        markStream.reset(mark);\n      }\n      Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);\n      if (bitmap == null) {\n        // Treat null as an IO exception, we will eventually retry.\n        throw new IOException(\"Failed to decode stream.\");\n      }\n      return bitmap;\n    }\n  }\n\n  @Override public void run() {\n    try {\n      updateThreadName(data);\n\n      if (picasso.loggingEnabled) {\n        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));\n      }\n\n      result = hunt();\n\n      if (result == null) {\n        dispatcher.dispatchFailed(this);\n      } else {","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/didi/DoKit/blob/626827cddb2feb2f3aee87a52a064b4e5ca2bed4/Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java#L127-L163","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nDokitPicasso.with(context).load(url).into avatarView;\n// IOException: Failed to decode stream. -> blank view\n\n// after\nDokitPicasso.with(context)\n    .load(url)\n    .error(R.drawable.avatar_placeholder)\n    .into(avatarView, new com.squareup.picasso.Callback.EmptyCallback() {});","handlingStrategy":"fallback","validationCode":"// Pre-flight check when the bytes come from your own source (optional):\nstatic boolean isDecodable(byte[] bytes) {\n  BitmapFactory.Options o = new BitmapFactory.Options();\n  o.inJustDecodeBounds = true;\n  BitmapFactory.decodeByteArray(bytes, 0, bytes.length, o);\n  return o.outWidth > 0 && o.outHeight > 0;\n}","typeGuard":null,"tryCatchPattern":"// It is an IOException routed to the error path — handle via callback + error drawable:\npicasso.load(url)\n    .error(R.drawable.broken)\n    .into(view, new Callback() {\n      @Override public void onSuccess() {}\n      @Override public void onError(Exception e) {\n        if (e instanceof IOException && \"Failed to decode stream.\".equals(e.getMessage())) {\n          reportBadImage(url); // e.g. fetch and inspect bytes server-side\n        }\n      }\n    });","preventionTips":["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"],"tags":["android","picasso","image-decoding","io"],"backgroundTag":null,"analyzedSha":"626827cddb2feb2f3aee87a52a064b4e5ca2bed4","analyzedAt":"2026-08-14T12:45:58.758Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}