Retrofit 2.0 how to get deserialised error response.body

JavaRetrofitRetrofit2

Java Problem Overview


I'm using Retrofit 2.0.0-beta1.

In tests i have an alternate scenario and expect error HTTP 400

I would like to have retrofit.Response<MyError> response but response.body() == null

MyError is not deserialised - i see it only here

response.errorBody().string()

but it doesn't give me MyError as object

Java Solutions


Solution 1 - Java

I currently use a very easy implementation, which does not require to use converters or special classes. The code I use is the following:

public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    DialogHelper.dismiss();

    if (response.isSuccessful()) {
        // Do your success stuff...
    } else {
        try {
            JSONObject jObjError = new JSONObject(response.errorBody().string());
            Toast.makeText(getContext(), jObjError.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }
}

A point to note here is that response.errorBody().string() will return the correct value only once. If you call it again, it will return an empty string. So in case you want to reuse it, store the value in a variable with the first call.

Solution 2 - Java

ErrorResponse is your custom response object

Kotlin

val gson = Gson()
val type = object : TypeToken<ErrorResponse>() {}.type
var errorResponse: ErrorResponse? = gson.fromJson(response.errorBody()!!.charStream(), type)
   

Java

Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse errorResponse = gson.fromJson(response.errorBody.charStream(),type);

Solution 3 - Java

I solved it by:

 if(!response.isSuccessful()){
       Gson gson = new Gson();
       MyErrorMessage message=gson.fromJson(response.errorBody().charStream(),MyErrorMessage.class);
       if(message.getCode()==ErrorCode.DUPLICATE_EMAIL_ID_CODE){
                  //DO Error Code specific handling                        
        }else{
                 //DO GENERAL Error Code Specific handling                               
        }
    }

MyErrorMessage Class:

  public class MyErrorMessage {
     private int code;
     private String message;

     public int getCode() {
        return code;
     }

     public void setCode(int code) {
        this.code = code;
     }

     public String getMessage() {
         return message;
     }

     public void setMessage(String message) {
        this.message = message;
     }
   }

Solution 4 - Java

It's actually very straight forward.

Kotlin:

val jsonObj = JSONObject(response.errorBody()!!.charStream().readText())
responseInterface.onFailure(jsonObj.getString("msg"))

Java:

    if(response.errorBody()!=null){
    JSONObject jsonObj = new JSONObject(TextStreamsKt.readText(response.errorBody().charStream()));
        responseInterface.onFailure(jsonObj.getString("msg"));
    }else{
        responseInterface.onFailure("you might want to return a generic error message.");
    }

Tested on retrofit:2.5.0. Read the text from the charStream which will give you a String, then parse to JSONObject.

Adios.

Solution 5 - Java

In Retrofit 2.0 beta2 this is the way that I'm getting error responses:

  1. Synchronous

    try {
       Call<RegistrationResponse> call = backendServiceApi.register(data.in.account, data.in.password,
               data.in.email);
       Response<RegistrationResponse> response = call.execute();
       if (response != null && !response.isSuccess() && response.errorBody() != null) {
           Converter<ResponseBody, BasicResponse> errorConverter =
                   MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
           BasicResponse error = errorConverter.convert(response.errorBody());
           //DO ERROR HANDLING HERE
           return;
       }
       RegistrationResponse registrationResponse = response.body();
       //DO SUCCESS HANDLING HERE
    } catch (IOException e) {
       //DO NETWORK ERROR HANDLING HERE
    }
    
  2. Asynchronous

    Call<BasicResponse> call = service.loadRepo();
    call.enqueue(new Callback<BasicResponse>() {
        @Override
        public void onResponse(Response<BasicResponse> response, Retrofit retrofit) {
            if (response != null && !response.isSuccess() && response.errorBody() != null) {
                Converter<ResponseBody, BasicResponse> errorConverter =
                    retrofit.responseConverter(BasicResponse.class, new Annotation[0]);
                BasicResponse error = errorConverter.convert(response.errorBody());
                //DO ERROR HANDLING HERE
                return;
            }
            RegistrationResponse registrationResponse = response.body();
            //DO SUCCESS HANDLING HERE
        }
    
        @Override
        public void onFailure(Throwable t) {
            //DO NETWORK ERROR HANDLING HERE
        }
    });
    

Update for Retrofit 2 beta3:

  1. Synchronous - not changed

  2. Asynchronous - Retrofit parameter was removed from onResponse

     Call<BasicResponse> call = service.loadRepo();
     call.enqueue(new Callback<BasicResponse>() {
         @Override
         public void onResponse(Response<BasicResponse> response) {
             if (response != null && !response.isSuccess() && response.errorBody() != null) {
                 Converter<ResponseBody, BasicResponse> errorConverter =
                     MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
                 BasicResponse error = errorConverter.convert(response.errorBody());
                 //DO ERROR HANDLING HERE
                 return;
             }
             RegistrationResponse registrationResponse = response.body();
             //DO SUCCESS HANDLING HERE
         }
    
         @Override
         public void onFailure(Throwable t) {
             //DO NETWORK ERROR HANDLING HERE
         }
     });
    

Solution 6 - Java

Create a model of the Error response & user Gson to convert the response to it. This will just work fine.

APIError.java

public class APIError {
    private String message;

    public String getMessage() {
        return message;
    }
}

MainActivity.java (inside request onResponse)

if (response.isSuccessful()) {
    // Do your success stuff...

} else {
    APIError message = new Gson().fromJson(response.errorBody().charStream(), APIError.class);
    Toast.makeText(MainActivity.this, "" + message.getMessage(), Toast.LENGTH_SHORT).show();
}

Solution 7 - Java

 @Override
 public void onResponse(Call<Void> call, retrofit2.Response<Void> response) {
            if (response.isSuccessful()) {
           
            //Do something if response is ok
            } else {
                
                JsonParser parser = new JsonParser();
                JsonElement mJson = null;
                try {
                    mJson = parser.parse(response.errorBody().string());
                    Gson gson = new Gson();
                    MyError errorResponse = gson.fromJson(mJson, MyError.class);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
          
            }
 

Solution 8 - Java

In https://stackoverflow.com/a/21103420/2914140 and https://futurestud.io/tutorials/retrofit-2-simple-error-handling this variant is shown for Retrofit 2.1.0.

call.enqueue(new Callback<MyResponse>() {
    @Override
    public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
        if (response.isSuccessful()) {
            ...
        } else {
            Converter<ResponseBody, MyError> converter
                    = MyApplication.getRetrofit().responseBodyConverter(
                    MyError.class, new Annotation[0]);
            MyError errorResponse = null;
            try {
                errorResponse = converter.convert(response.errorBody());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Solution 9 - Java

If you use Kotlin another solution could be just create extension function for Response class:

inline fun <reified T>Response<*>.parseErrJsonResponse(): T?
{
    val moshi = MyCustomMoshiBuilder().build()
    val parser = moshi.adapter(T::class.java)
    val response = errorBody()?.string()
    if(response != null)
        try {
            return parser.fromJson(response)
        } catch(e: JsonDataException) {
            e.printStackTrace()
        }
    return null
}

Usage

val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
   // handle your error logic here
   // ...
}


   

Solution 10 - Java

I did it this way for asynchronous calls using Retrofit 2.0-beta2:

@Override
public void onResponse(Response<RegistrationResponse> response, 
                       Retrofit retrofit) {
    if (response.isSuccess()) {
        // Do success handling here
    } else {
        try {
            MyError myError = (MyError)retrofit.responseConverter(
                    MyError.class, MyError.class.getAnnotations())
                .convert(response.errorBody());
            // Do error handling here
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Solution 11 - Java

I was facing same issue. I solved it with retrofit. Let me show this...

If your error JSON structure are like

{
"error": {
    "status": "The email field is required."
}
}


My ErrorRespnce.java 

public class ErrorResponse {

   @SerializedName("error")
   @Expose
   private ErrorStatus error;

   public ErrorStatus getError() {
      return error;
   }

   public void setError(ErrorStatus error) {
      this.error = error;
   }
}

And this my Error status class

public class ErrorStatus {

  @SerializedName("status")
  @Expose
  private String status;

  public String getStatus() {
      return status;
  }

  public void setStatus(String status) {
      this.status = status;
  }
}

Now we need a class which can handle our json.

  public class ErrorUtils {

   public static ErrorResponse parseError (Response<?> response){
      Converter<ResponseBody , ErrorResponse> converter =          ApiClient.getClient().responseBodyConverter(ErrorResponse.class , new Annotation[0]);
    ErrorResponse errorResponse;
    try{
        errorResponse = converter.convert(response.errorBody());
    }catch (IOException e){
        return new ErrorResponse();
    }
    return errorResponse;
}
}

Now we can check our response in retrofit api call

private void registrationRequest(String name , String email , String password , String c_password){
   

    final Call<RegistrationResponce> registrationResponceCall = apiInterface.getRegistration(name , email , password , c_password);
    registrationResponceCall.enqueue(new Callback<RegistrationResponce>() {
        @Override
        public void onResponse(Call<RegistrationResponce> call, Response<RegistrationResponce> response) {

        

            if (response.code() == 200){


            }else if (response.code() == 401){

               
                ErrorResponse errorResponse = ErrorUtils.parseError(response);
                Toast.makeText(MainActivity.this, ""+errorResponse.getError().getStatus(), Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onFailure(Call<RegistrationResponce> call, Throwable t) {

        }
    });
}

That's it now you can show your Toast

Solution 12 - Java

if(!response.isSuccessful()) {
    StringBuilder error = new StringBuilder();
    try {
        BufferedReader bufferedReader = null;
        if (response.errorBody() != null) {
            bufferedReader = new BufferedReader(new InputStreamReader(
                    response.errorBody().byteStream()));

            String eLine = null;
            while ((eLine = bufferedReader.readLine()) != null) {
                error.append(eLine);
            }
            bufferedReader.close();
        }

    } catch (Exception e) {
        error.append(e.getMessage());
    }

    Log.e("Error", error.toString());
}

Solution 13 - Java

Here is elegant solution using Kotlin extensions:

data class ApiError(val code: Int, val message: String?) {
    companion object {
        val EMPTY_API_ERROR = ApiError(-1, null)
    }
}

fun Throwable.getApiError(): ApiError? {
    if (this is HttpException) {
        try {
            val errorJsonString = this.response()?.errorBody()?.string()
            return Gson().fromJson(errorJsonString, ApiError::class.java)
        } catch (exception: Exception) {
            // Ignore
        }
    }
    return EMPTY_API_ERROR
}

and usage:

showError(retrofitThrowable.getApiError()?.message)

Solution 14 - Java

This way you do not need a Retrofit instance if you only are injecting a service created from Retrofit.

public class ErrorUtils {

  public static APIError parseError(Context context, Response<?> response) {

    APIError error = new APIError();

    try {
        Gson gson = new Gson();
        error = gson.fromJson(response.errorBody().charStream(), APIError.class);
    } catch (Exception e) {
        Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
    }

    if (TextUtils.isEmpty(error.getErrorMessage())) {
        error.setError(response.raw().message());
    }
    return error;
  }
}

Use it like this:

if (response.isSuccessful()) {

      ...

    } else {

      String msg = ErrorUtils.parseError(fragment.getActivity(), response).getError(); // would be from your error class
      Snackbar.make(someview, msg, Snackbar.LENGTH_LONG).show();
    }
  }

Solution 15 - Java

There are many valid answers already. This is just an addition for a use case, when you need to consume same Retrofit response more than once. Neither of below can be used, as you can read response body only once, as it will be closed afterwards and you will get null each next time, when you try to read from the same response object:

response()?.errorBody()?.charStream()?.readText()
response()?.errorBody()?.string()

Instead, you can get read-only copy of response string (while the response itself can be passed over and eventually consumed later):

response()?.errorBody()?.source()?.buffer?.snapshot()?.utf8()

Solution 16 - Java

This seems to be the problem when you use OkHttp along with Retrofit, so either you can remove OkHttp or use code below to get error body:

if (!response.isSuccessful()) {
 InputStream i = response.errorBody().byteStream();
 BufferedReader r = new BufferedReader(new InputStreamReader(i));
 StringBuilder errorResult = new StringBuilder();
 String line;
 try {
   while ((line = r.readLine()) != null) {
   errorResult.append(line).append('\n');
   }
 } catch (IOException e) { 
    e.printStackTrace(); 
}
}

Solution 17 - Java

if your error response is a string you can deserialize it by using the following kotlin code :

val errorString = response.errorBody()?.byteStream()?.bufferedReader().use { it?.readText() }  // defaults to UTF-8

Solution 18 - Java

json response

{
    "success": false,
    "status_code": 32,
    "status_message": "Email not verified: Your email address has not been verified."
}

Error class

data class ResponseError(
    @SerializedName("status_code")
    val statusCode: Int,
    @SerializedName("status_message")
    val statusMessage: String,
    @SerializedName("success")
    val success: Boolean
)

get error message

fun <T : Any> getResultOrError(response: Response<T>): T? {
    if (response.isSuccessful) {
        return response.body()
    } else {
        try {
            val responseError = Gson().fromJson(
                response.errorBody()?.string(),
                ResponseError::class.java
            )
            throw Throwable(responseError.statusMessage)
        } catch (e: Exception) {
            throw Throwable("Unknown error")
        }
    }
}

Solution 19 - Java

solved it by:

Converter<MyError> converter = 
    (Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError =  converter.fromBody(response.errorBody());

Solution 20 - Java

try{
                ResponseBody response = ((HttpException) t).response().errorBody();
                JSONObject json = new JSONObject( new String(response.bytes()) );
                errMsg = json.getString("message");
            }catch(JSONException e){
                return t.getMessage();
            }
            catch(IOException e){
                return t.getMessage();
            }

Solution 21 - Java

In Kotlin:

val call = APIClient.getInstance().signIn(AuthRequestWrapper(AuthRequest("1234567890z", "12341234", "nonce")))
call.enqueue(object : Callback<AuthResponse> {
    override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
        if (response.isSuccessful) {

        } else {
            val a = object : Annotation{}
            val errorConverter = RentalGeekClient.getRetrofitInstance().responseBodyConverter<AuthFailureResponse>(AuthFailureResponse::class.java, arrayOf(a))
            val authFailureResponse = errorConverter.convert(response.errorBody())
        }
    }

    override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
    }
})

Solution 22 - Java

errorBody values should set APIError object in Retrofit. So that, you can use the below code structure.

public class APIErrorUtils {

    public static APIError parseError(Response<?> response) {
        Converter<ResponseBody, APIError> converter = API.getClient().responseBodyConverter(APIError.class, new Annotation[0]);

        APIError error;

        try {
            error = converter.convert(response.errorBody());
            Log.d("SERVICELOG", "****************************************************");
            Log.d("SERVICELOG", "***** SERVICE LOG");
            Log.d("SERVICELOG", "***** TIMESTAMP: " + String.valueOf(error.getTimestamp()));
            Log.d("SERVICELOG", "***** STATUS: " + String.valueOf(error.getStatus()));
            Log.d("SERVICELOG", "***** ERROR: " + error.getError());
            Log.d("SERVICELOG", "***** MESSAGE: " + error.getMessage());
            Log.d("SERVICELOG", "***** PATH: " + error.getPath());
            Log.d("SERVICELOG", "****************************************************");
        } catch (IOException e) {
            return new APIError();
        }

        return error;
    }
}

APIError error = APIErrorUtils.parseError(response);
if (error.getStatus() == 400) {
    ....
}

Solution 23 - Java

Tested and works

 public BaseModel parse(Response<BaseModel> response , Retrofit retrofit){
            BaseModel error = null;
            Converter<ResponseBody, BaseModel> errorConverter =
                    retrofit.responseBodyConverter(BaseModel.class, new Annotation[0]);
            try {
                if (response.errorBody() != null) {
                    error = errorConverter.convert(response.errorBody());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return error;
        }

Solution 24 - Java

val error = JSONObject(callApi.errorBody()?.string() as String)
            CustomResult.OnError(CustomNotFoundError(userMessage = error["userMessage"] as String))

open class CustomError (
    val traceId: String? = null,
    val errorCode: String? = null,
    val systemMessage: String? = null,
    val userMessage: String? = null,
    val cause: Throwable? = null
)

open class ErrorThrowable(
    private val traceId: String? = null,
    private val errorCode: String? = null,
    private val systemMessage: String? = null,
    private val userMessage: String? = null,
    override val cause: Throwable? = null
) : Throwable(userMessage, cause) {
    fun toError(): CustomError = CustomError(traceId, errorCode, systemMessage, userMessage, cause)
}


class NetworkError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage?: "Usted no tiene conexión a internet, active los datos", cause)

class HttpError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage, cause)

class UnknownError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage?: "Unknown error", cause)

class CustomNotFoundError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage?: "Data not found", cause)`

Solution 25 - Java

Error body handling in kotlin Android

catch (cause: Throwable) {
            when (cause) {
                is HttpException -> {
                    try {
                        val YourErrorResponseClassObj = Gson().fromJson(cause.response()?.errorBody()?.charStream(), YourErrorResponseClass::class.java)
                    } catch (e: Exception) {
                        
                    }
                }
                else -> {
                    //Other errors like Network ...
                }
            }
        }

Solution 26 - Java

very simple. and this save my life ever

public static void displayApiResponseErrorBody(Response<?> response)
{
    InputStream i = response.errorBody().byteStream();
    BufferedReader r = new BufferedReader(new InputStreamReader(i));
    StringBuilder errorResult = new StringBuilder();
    String line;
    try {
        while ((line = r.readLine()) != null) 
        {
            errorResult.append(line).append('\n');
        }
        Log.d("API_RESPONSE_ERROR_BODY",String.valueOf(errorResult));
        System.out.println(errorResult);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Solution 27 - Java

In case of retrofit error Response, You can get body using error.getResponse(), Here is the example.

        @Override
        public void failure(RetrofitError error){
            if(error.getResponse().getStatus()==201){
                LogUtil.INSTANCE.debug("Success : " + error.toString());
                callback.success(error.getResponse().getBody);
            }else{
                LogUtil.INSTANCE.debug("failure: " + error.toString());
                callback.failure(error);
            }
        }

Solution 28 - Java

In Kotlin I solved it creating a custom ResponseBody generic extension function function that converts the response body to a JSONObject. then you can use gson to customize the error response body with your custom Error Data Class.

inline fun <reified T> ResponseBody.getErrorObject(): T {
    val gson = Gson()
    val jsonObject = JSONObject(charStream().readText())
    return gson.fromJson(jsonObject.toString(), T::class.java)
}

You can then customer the error response to your custom class. For this I'm using an example

data class LoginError(
    val error: Error,
    val message: String,
    val success: Boolean
)

data class Error(
    val error: String,
    val status: Int
)

then use the extension function this way

val error = state.errorBody.getErrorObject<LoginError>()

the state.errorBody is my error response from retrofit of type ResponseBody

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
QuestionPiotr BohoView Question on Stackoverflow
Solution 1 - JavaSaif BechanView Answer on Stackoverflow
Solution 2 - JavaShahab RaufView Answer on Stackoverflow
Solution 3 - JavaPooja GuptaView Answer on Stackoverflow
Solution 4 - JavaWaleView Answer on Stackoverflow
Solution 5 - JavaJFreemanView Answer on Stackoverflow
Solution 6 - JavaSreekant ShenoyView Answer on Stackoverflow
Solution 7 - JavaVinsView Answer on Stackoverflow
Solution 8 - JavaCoolMindView Answer on Stackoverflow
Solution 9 - JavaArseniusView Answer on Stackoverflow
Solution 10 - JavaShantanuView Answer on Stackoverflow
Solution 11 - JavapavelView Answer on Stackoverflow
Solution 12 - JavaRiyas PKView Answer on Stackoverflow
Solution 13 - JavaAntonis RadzView Answer on Stackoverflow
Solution 14 - JavaCodeversedView Answer on Stackoverflow
Solution 15 - JavaMyroslavView Answer on Stackoverflow
Solution 16 - JavaKRUPEN GHETIYAView Answer on Stackoverflow
Solution 17 - Javaa0x2View Answer on Stackoverflow
Solution 18 - JavaRuslan GrigorievView Answer on Stackoverflow
Solution 19 - JavaPiotr BohoView Answer on Stackoverflow
Solution 20 - JavaMike6679View Answer on Stackoverflow
Solution 21 - JavaAdam JohnsView Answer on Stackoverflow
Solution 22 - JavaEgemen MedeView Answer on Stackoverflow
Solution 23 - JavaThe MJView Answer on Stackoverflow
Solution 24 - JavaGary LoyolaView Answer on Stackoverflow
Solution 25 - JavaAbhishek GargView Answer on Stackoverflow
Solution 26 - Javahamil.DevView Answer on Stackoverflow
Solution 27 - JavaFarid HaqView Answer on Stackoverflow
Solution 28 - JavaEzekiel WachiraView Answer on Stackoverflow