Accessing body string of an OkHttp Response twice results in IllegalStateException: closed

AndroidOkhttp

Android Problem Overview


I implement my http calls via the OkHttp library. Everything works fine, but I noticed that, when I access the body as a string of the response twice an IllegalStateException will be thrown. That is, I do (for example): Log.d("TAG", response.body().string()) and after that I actually want to use that string like processResponse(response.body().string()). But that second call throws the exception with the message closed.

How can it be possible that accessing a string twice results in a failure? I want to process that response without the need to add a wrapper/dummy object just for saving some values (like header, body, statuscode).

Android Solutions


Solution 1 - Android

Update:

As mugwort points out there is a simpler, more lightweight API available now (see GitHub issue):

String responseBodyString = response.peekBody(Long.MAX_VALUE).string();
Log.d("TAG", responseBodyString);

Original Answer:

For explanation of the issue see Greg Ennis' answer.

However, if you can not easily pass the result in a variable, but still need to access the response body twice you have another option:

Clone the buffer before reading it. By that the original buffer is neither emptied nor closed. See this snippet:

ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // request the entire body.
Buffer buffer = source.buffer();
// clone buffer before reading from it
String responseBodyString = buffer.clone().readString(Charset.forName("UTF-8"))
Log.d("TAG", responseBodyString);

This approach is used in HttpLoggingInterceptor in project okhttp by square themselves.

Solution 2 - Android

The string method on the response will read the input (network) stream and convert it into a string. So it dynamically builds the string and returns it to you. The second time you call it, the network stream has already been consumed and is no longer available.

You should save the result of string into a String variable, and then access it as many times as needed.

Solution 3 - Android

ResponseBody body = response.peekBody(Long.MAX_VALUE);
String content = body.string();
//do something

this code get the response body and won't consume the buffer. it is a new api added in this issue

Solution 4 - Android

Expanding a little on the answers of user2011622 and Greg Ennis, I created a method which helps you to create a complete clone of the body, allowing you to consume each copy of the body separately. I use this method myself with Retrofit2

/**
 * Clones a raw buffer so as not to consume the original
 * @param rawResponse the original {@link okhttp3.Response} as returned
 *                    by {@link Response#raw()}
 * @return a cloned {@link ResponseBody}
 */
private ResponseBody cloneResponseBody(okhttp3.Response rawResponse) {
    final ResponseBody responseBody = rawResponse.body();
    final Buffer bufferClone = responseBody.source().buffer().clone();
    return ResponseBody.create(responseBody.contentType(), responseBody.contentLength(), bufferClone);
}

Solution 5 - Android

BTW in addition to Greg Ennis' answer I can tell what onetime this happened to me when I forgot response.body().string() in the watch window. So under the debugger the body was reading into the watch and the network stream was closed after that.

Solution 6 - Android

You can call response.peekBody(Long.MAX_VALUE); to buffer the entire response in memory and get a lightweight copy of it. It'll be much less wasteful. See this issue on GitHub.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondegillView Question on Stackoverflow
Solution 1 - AndroidPeter FView Answer on Stackoverflow
Solution 2 - AndroidGreg EnnisView Answer on Stackoverflow
Solution 3 - AndroidironmanView Answer on Stackoverflow
Solution 4 - AndroidNick CardosoView Answer on Stackoverflow
Solution 5 - AndroidAlexandrView Answer on Stackoverflow
Solution 6 - AndroidmugwortView Answer on Stackoverflow