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

  1. Check HTTP response code before reading: cast to HttpURLConnection, verify responseCode == 200, and surface error stream/ status instead of decoding blindly.
  2. Verify the API key in the widget settings is valid and not expired; test the same URL with curl.
  3. Regenerate missing thumbnails (run thumbnail generation jobs in Immich admin) for assets whose preview is absent.
  4. 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

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


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)