title: Retrofit fails to download binary file in an Android app – end result is a corrupted file that is larger than expected
body: <p>I am trying to download PDF files using Retrofit 2 on an Android app written in Kotlin. The snippet below is basically my entire code. Based on my log output, it appears that the file downloads successfully and saves to the intended location. </p>
  
  <p>However, the downloaded file is larger than expected, and is corrupt. I can open it in a PDF reader, but the PDF is blank. In the example below, I tried downloading <a href="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" rel="nofollow noreferrer">https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf</a>. If I download this file through a browser, the result is a PDF that is 13,264 bytes. Downloaded with this code however, it is 22,503 bytes, about 70% larger than expected. I am getting a similar result for other binary files like JPEGs. However, downloading a TXT actually works fine, even a large one. So it seems like the problem is isolated to binary files.</p>
  
  <pre><code>package com.ebelinski.RetrofitTestApp
  
  import android.app.Application
  import android.content.Context
  import android.os.Build
  import android.support.v7.app.AppCompatActivity
  import android.os.Bundle
  import android.util.Log
  import com.google.gson.FieldNamingPolicy
  import com.google.gson.GsonBuilder
  import okhttp3.OkHttpClient
  import okhttp3.ResponseBody
  import org.jetbrains.anko.doAsync
  import retrofit2.Call
  import retrofit2.http.*
  import retrofit2.Retrofit
  import retrofit2.converter.gson.GsonConverterFactory
  import java.io.*
  import java.util.concurrent.Executors
  import java.util.concurrent.TimeUnit
  
  interface FileService {
  
      @Streaming
      @GET
      @Headers("Content-Type: application/pdf", "Accept: application/pdf")
      fun fileFromUrl(@Url url: String,
                      @Header("Authorization") tokenTypeWithAuthorization: String): Call&lt;ResponseBody&gt;
  
  }
  
  class MainActivity : AppCompatActivity() {
  
      val TAG = "MainActivity"
  
      val RETROFIT_CONNECT_TIMEOUT_SECONDS = 60
      private val RETROFIT_READ_TIMEOUT_SECONDS = 60
      private val RETROFIT_WRITE_TIMEOUT_SECONDS = 60
  
      private val retrofit: Retrofit
          get() {
              val gson = GsonBuilder()
                  .setDateFormat("yyyyMMdd")
                  .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                  .create()
  
              val converterFactory = GsonConverterFactory.create(gson)
  
              val okHttpClient = OkHttpClient.Builder()
                  .connectTimeout(RETROFIT_CONNECT_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
                  .readTimeout(RETROFIT_READ_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
                  .writeTimeout(RETROFIT_WRITE_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
                  .addInterceptor { chain -&gt;
                      val userAgentValue = "doesn't matter"
                      val originalRequest = chain.request().newBuilder().addHeader("User-Agent", userAgentValue).build()
  
                      var response = chain.proceed(originalRequest)
                      if (BuildConfig.DEBUG) {
                          val bodyString = response.body()!!.string()
                          Log.d(TAG, String.format("Sending request %s with headers %s ", originalRequest.url(), originalRequest.headers()))
                          Log.d(TAG, String.format("Got response HTTP %s %s \n\n with body %s \n\n with headers %s ", response.code(), response.message(), bodyString, response.headers()))
                          response = response.newBuilder().body(ResponseBody.create(response.body()!!.contentType(), bodyString)).build()
                      }
  
                      response
                  }
                  .build()
  
              return Retrofit.Builder()
                  .callbackExecutor(Executors.newCachedThreadPool())
                  .baseUrl("https://example.com")
                  .addConverterFactory(converterFactory)
                  .client(okHttpClient)
                  .build()
          }
  
      private val fileService: FileService = retrofit.create(FileService::class.java)
  
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
  
          doAsync { downloadFile() }
      }
  
      fun downloadFile() {
          val uri = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
          val auth = "doesn't matter"
  
          val response = fileService.fileFromUrl(
              uri,
              auth
          ).execute()
  
          if (!response.isSuccessful) {
              Log.e(TAG, "response was not successful: " +
                      response.code() + " -- " + response.message())
              throw Throwable(response.message())
          }
  
          Log.d(TAG, "Server has file for ${uri}")
          saveFileFromResponseBody(response.body()!!)
      }
  
  
  
      // Returns the name of what the file should be, whether or not it exists locally
      private fun getFileName(): String? {
          return "dummy.pdf"
      }
  
      fun saveFileFromResponseBody(body: ResponseBody): Boolean {
          val fileName = getFileName()
          val localFullFilePath = File(getFileFullDirectoryPath(), fileName)
          var inputStream: InputStream? = null
          var outputStream: OutputStream? = null
          Log.d(TAG, "Attempting to download $fileName")
  
          try {
              val fileReader = ByteArray(4096)
              val fileSize = body.contentLength()
              var fileSizeDownloaded: Long = 0
  
              inputStream = body.byteStream()
              outputStream = FileOutputStream(localFullFilePath)
  
              while (true) {
                  val read = inputStream.read(fileReader)
                  if (read == -1) break
  
                  outputStream.write(fileReader, 0, read)
                  fileSizeDownloaded += read.toLong()
  
                  Log.d(TAG, "$fileName download progress: $fileSizeDownloaded of $fileSize")
              }
  
              outputStream.flush()
              Log.d(TAG, "$fileName downloaded successfully")
              return true
          } catch (e: IOException) {
              Log.d(TAG, "$fileName downloaded attempt failed")
              return false
          } finally {
              inputStream?.close()
              outputStream?.close()
          }
      }
  
      fun getFileFullDirectoryPath(): String {
          val directory = getDir("test_dir", Context.MODE_PRIVATE)
          return directory.absolutePath
      }
  }
  </code></pre>
  
  <p>If it helps, here's my <code>build.gradle</code> file:</p>
  
  <pre><code>apply plugin: 'com.android.application'
  
  apply plugin: 'kotlin-android'
  
  apply plugin: 'kotlin-android-extensions'
  
  android {
      compileSdkVersion 28
      defaultConfig {
          applicationId "com.ebelinski.RetrofitTestApp"
          minSdkVersion 21
          targetSdkVersion 28
          versionCode 1
          versionName "1.0"
          testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
      }
      buildTypes {
          release {
              minifyEnabled false
              proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
          }
      }
  }
  
  dependencies {
      implementation fileTree(dir: 'libs', include: ['*.jar'])
      implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
      implementation 'com.android.support:appcompat-v7:28.0.0'
      implementation 'com.android.support.constraint:constraint-layout:1.1.3'
      implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
      implementation 'com.squareup.retrofit2:retrofit:2.5.0'
      implementation 'com.squareup.okhttp3:okhttp:3.12.0'
      implementation "org.jetbrains.anko:anko-commons:0.10.1"
      testImplementation 'junit:junit:4.12'
      androidTestImplementation 'com.android.support.test:runner:1.0.2'
      androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
  }
  </code></pre>