{"record":{"id":"09714135db315ec5","repo":"immich-app/immich","slug":"invalid-image-data","errorCode":null,"errorMessage":"Invalid image data","messagePattern":"Invalid image data","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt","lineNumber":108,"sourceCode":"  suspend fun fetchMemory(date: LocalDate): List<MemoryResult> = withContext(Dispatchers.IO) {\n    val iso8601 = date.format(DateTimeFormatter.ISO_LOCAL_DATE)\n    val url = buildRequestURL(\"/memories\", listOf(\"for\" to iso8601))\n    val connection = (url.openConnection() as HttpURLConnection).apply {\n      requestMethod = \"GET\"\n      applyCustomHeaders()\n    }\n\n    val response = connection.inputStream.bufferedReader().readText()\n    val type = object : TypeToken<List<MemoryResult>>() {}.type\n    gson.fromJson(response, type)\n  }\n\n  suspend fun fetchImage(asset: Asset): Bitmap = withContext(Dispatchers.IO) {\n    val url = buildRequestURL(\"/assets/${asset.id}/thumbnail\", listOf(\"size\" to \"preview\", \"edited\" to \"true\"))\n    val connection = url.openConnection()\n    val data = connection.getInputStream().readBytes()\n    BitmapFactory.decodeByteArray(data, 0, data.size)\n      ?: throw Exception(\"Invalid image data\")\n  }\n\n  suspend fun fetchAlbums(): List<Album> = withContext(Dispatchers.IO) {\n    val url = buildRequestURL(\"/albums\")\n    val connection = (url.openConnection() as HttpURLConnection).apply {\n      requestMethod = \"GET\"\n      applyCustomHeaders()\n    }\n\n    val response = connection.inputStream.bufferedReader().readText()\n    val type = object : TypeToken<List<Album>>() {}.type\n    gson.fromJson(response, type)\n  }\n}\n","sourceCodeStart":90,"sourceCodeEnd":123,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt#L90-L123","documentation":"Immich's Android widget ImmichAPI.fetchImage downloads the asset's preview thumbnail via /assets/{id}/thumbnail and decodes it with BitmapFactory.decodeByteArray. decodeByteArray returns null for undecodable bytes, and the code converts that null into Exception(\"Invalid image data\"). This happens whenever the response body is not valid image bytes — most often an HTTP error page/JSON error instead of an image, since the response status is never checked.","triggerScenarios":"Server returns 401/403 (missing or expired API key/session), 404 (asset deleted or thumbnail not yet generated), or an HTML/JSON error body, and decodeByteArray fails on those bytes; also a truncated/partial download over a flaky connection.","commonSituations":"Widget used without a valid API key or after the key was rotated; offline or captive-portal network causing an error page as response; random-asset/memory queries returning an asset whose preview hasn't been generated yet.","solutions":["Check HTTP response code before reading: cast to HttpURLConnection, verify responseCode == 200, and surface error stream/ status instead of decoding blindly.","Verify the API key in the widget settings is valid and not expired; test the same URL with curl.","Regenerate missing thumbnails (run thumbnail generation jobs in Immich admin) for assets whose preview is absent.","Retry the fetch on network failure and check for connectivity before calling the API."],"exampleFix":"// before\nval connection = url.openConnection()\nval data = connection.getInputStream().readBytes()\nBitmapFactory.decodeByteArray(data, 0, data.size) ?: throw Exception(\"Invalid image data\")\n// after\nval connection = url.openConnection() as HttpURLConnection\nif (connection.responseCode != 200) throw Exception(\"HTTP ${connection.responseCode}\")\nval data = connection.inputStream.readBytes()\nBitmapFactory.decodeByteArray(data, 0, data.size) ?: throw Exception(\"Invalid image data\")","handlingStrategy":"try-catch","validationCode":"// check reachability + auth before fetching\nval connection = buildRequestURL(\"/assets/${asset.id}/thumbnail\", listOf(\"size\" to \"preview\", \"edited\" to \"true\")).openConnection() as HttpURLConnection\nconnection.setRequestProperty(\"x-api-key\", apiKey)\nval ok = connection.responseCode == 200\nif (!ok) throw Exception(\"Thumbnail request failed: HTTP ${connection.responseCode}\")","typeGuard":"fun ByteArray?.isValidBitmap(): Boolean =\n  this != null && BitmapFactory.decodeByteArray(this, 0, size) != null","tryCatchPattern":"try {\n  val bitmap = fetchImage(asset)\n  render(bitmap)\n} catch (e: Exception) {\n  Log.w(TAG, \"Widget image fetch failed for ${asset.id}: ${e.message}\")\n  renderPlaceholder() // retry on next widget refresh\n}","preventionTips":["Always check HttpURLConnection.responseCode before decoding the body as an image.","Store and refresh a valid API key in widget settings; handle 401 by prompting re-setup.","Regenerate missing thumbnails server-side; not every asset has a preview immediately after upload.","Add retry/backoff for transient network failures and show a placeholder on failure."],"tags":["android","immich","image-decoding","http","thumbnail"],"backgroundTag":"http-error-response","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}