immich-app/immich · error · Exception
Invalid image data
Error message
Invalid image data
What it means
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.
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.
Example fix
// before
val connection = url.openConnection()
val data = connection.getInputStream().readBytes()
BitmapFactory.decodeByteArray(data, 0, data.size) ?: throw Exception("Invalid image data")
// after
val connection = url.openConnection() as HttpURLConnection
if (connection.responseCode != 200) throw Exception("HTTP ${connection.responseCode}")
val data = connection.inputStream.readBytes()
BitmapFactory.decodeByteArray(data, 0, data.size) ?: throw Exception("Invalid image data") Defensive patterns
Strategy: try-catch
Validate before calling
// check reachability + auth before fetching
val connection = buildRequestURL("/assets/${asset.id}/thumbnail", listOf("size" to "preview", "edited" to "true")).openConnection() as HttpURLConnection
connection.setRequestProperty("x-api-key", apiKey)
val ok = connection.responseCode == 200
if (!ok) throw Exception("Thumbnail request failed: HTTP ${connection.responseCode}") Type guard
fun ByteArray?.isValidBitmap(): Boolean = this != null && BitmapFactory.decodeByteArray(this, 0, size) != null
Try / catch
try {
val bitmap = fetchImage(asset)
render(bitmap)
} catch (e: Exception) {
Log.w(TAG, "Widget image fetch failed for ${asset.id}: ${e.message}")
renderPlaceholder() // retry on next widget refresh
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- EOFException
- Asset dimensions are not available for editing
- Asset media not found
- Asset not in stack
- await response.text()
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/09714135db315ec5.
Report an issue: GitHub.
Appendix: source
Thrown at mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt:108
suspend fun fetchMemory(date: LocalDate): List<MemoryResult> = withContext(Dispatchers.IO) {
val iso8601 = date.format(DateTimeFormatter.ISO_LOCAL_DATE)
val url = buildRequestURL("/memories", listOf("for" to iso8601))
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
applyCustomHeaders()
}
val response = connection.inputStream.bufferedReader().readText()
val type = object : TypeToken<List<MemoryResult>>() {}.type
gson.fromJson(response, type)
}
suspend fun fetchImage(asset: Asset): Bitmap = withContext(Dispatchers.IO) {
val url = buildRequestURL("/assets/${asset.id}/thumbnail", listOf("size" to "preview", "edited" to "true"))
val connection = url.openConnection()
val data = connection.getInputStream().readBytes()
BitmapFactory.decodeByteArray(data, 0, data.size)
?: throw Exception("Invalid image data")
}
suspend fun fetchAlbums(): List<Album> = withContext(Dispatchers.IO) {
val url = buildRequestURL("/albums")
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
applyCustomHeaders()
}
val response = connection.inputStream.bufferedReader().readText()
val type = object : TypeToken<List<Album>>() {}.type
gson.fromJson(response, type)
}
}
View on GitHub (pinned to e55ac299a4)