immich-app/immich · error · EOFException

EOFException

Error message

EOFException

What it means

In RemoteImagesImpl.kt's OkHttp onResponse callback, the response body is streamed into a native buffer. If source.read returns -1 (stream ended) before the expected length has been fully read, an EOFException is thrown — the HTTP response was truncated mid-body.

Solutions

  1. Retry the download — OkHttp/Call retries or an app-level retry with backoff usually succeeds on a stable connection.
  2. Check server and reverse-proxy timeout/buffering settings (e.g. nginx proxy_read_timeout) so large responses aren't truncated.
  3. Verify server health/logs for connection resets during the transfer.
  4. Consider range-based resumable downloads for large originals.

Example fix

// before
val bytes = downloadOnce(url)
// after
val bytes = runCatching { downloadOnce(url) }.getOrElse { e ->
  if (e is EOFException) retryWithBackoff(3) { downloadOnce(url) } else throw e
}
Defensive patterns

Strategy: retry

Try / catch

try {
  downloadAndBuffer(response)
} catch (e: EOFException) {
  retryWithBackoff(maxAttempts = 3) { redownload(url) }
}

Prevention

When it happens

Trigger: Server closes the connection after sending fewer bytes than Content-Length promised; interrupted network transfer mid-download of the original image; proxy/load-balancer timeouts cutting the response short.

Common situations: Flaky mobile networks during full-quality remote image downloads; Immich server restarts or reverse-proxy (nginx) buffering timeouts; reading via wifi-to-cellular handoff.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/eccf29b927cb5b0a. Report an issue: GitHub.

Appendix: source

Thrown at mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt:477

          }

          val body = response.body
            ?: return onFailure(IOException("Empty response body")).also { onComplete() }

          if (call.isCanceled()) {
            onFailure(OperationCanceledException())
            return onComplete()
          }

          body.source().use { source ->
            val length = body.contentLength().toInt()
            val buffer = NativeByteBuffer(if (length > 0) length else INITIAL_BUFFER_SIZE)
            try {
              if (length > 0) {
                val wrapped = NativeBuffer.wrap(buffer.pointer, length)
                while (wrapped.hasRemaining()) {
                  if (call.isCanceled()) throw OperationCanceledException()
                  if (source.read(wrapped) == -1) throw EOFException()
                }
                buffer.advance(length)
              } else {
                while (true) {
                  if (call.isCanceled()) throw OperationCanceledException()
                  val bytesRead = source.read(buffer.wrapRemaining())
                  if (bytesRead == -1) break
                  buffer.advance(bytesRead)
                  buffer.ensureHeadroom()
                }
              }
              onSuccess(buffer, response.header("Content-Type"))
            } catch (e: Exception) {
              buffer.free()
              onFailure(e)
            }
            onComplete()
          }

View on GitHub (pinned to e55ac299a4)