165

Recently I started using Retrofit 2 and I faced an issue with parsing empty response body. I have a server which responds only with http code without any content inside the response body.

How can I handle only meta information about server response (headers, status code etc)?

H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144
Yevgen Derkach
  • 1,688
  • 2
  • 11
  • 10

8 Answers8

257

Edit:

As Jake Wharton points out,

@GET("/path/to/get")
Call<Void> getMyData(/* your args here */);

is the best way to go versus my original response --

You can just return a ResponseBody, which will bypass parsing the response.

@GET("/path/to/get")
Call<ResponseBody> getMyData(/* your args here */);

Then in your call,

Call<ResponseBody> dataCall = myApi.getMyData();
dataCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Response<ResponseBody> response) {
        // use response.code, response.headers, etc.
    }

    @Override
    public void onFailure(Throwable t) {
        // handle failure
    }
});
iagreen
  • 31,470
  • 8
  • 76
  • 90
  • 68
    Even better: Use `Void` which not only has better semantics but is (slightly) more efficient in the empty case and vastly more efficient in a non-empty case (when you just don't care about body). – Jake Wharton Oct 20 '15 at 06:25
  • 2
    @JakeWharton That is great behavior. Thanks for the pointing it out. Answer updated. – iagreen Oct 20 '15 at 06:41
  • How can i parse the single string data returned in response??Like **"Some Value"**. – iSrinivasan27 Jun 14 '16 at 11:55
  • 3
    Great answer. One reason to not use Void is if you have a resource that only returns a body when the request is unsuccessful and you want to convert the errorBody ResponseBody to some specific or common type. –  Apr 27 '17 at 17:04
  • 10
    @JakeWharton Great suggestion to use `Void`. Would using `Unit` in Kotlin code give the same benefit of `Void` in Java for Retrofit? – Akshay Chordiya Aug 25 '17 at 09:28
  • @jdv what would be the alternative in not using void? Response – Jono Jan 15 '18 at 16:48
  • 2
    @jonney I have an API with an endpoint that returns a response body only on error. I defined it as `Call` and only look at it in the `onFailure` handler. It is a no-op param in the `onResponse` handler. –  Jan 15 '18 at 18:23
  • I have to also get the response headers and as far as i see, the ResponseBody does not contain any headers in the response, just the body – Jono Jan 16 '18 at 10:23
  • @jonney If you `execute()` a `Call`, you'll get a `Response`, and Retrofit `Response` has a method `headers()` to get them. – JavierSA Jul 31 '18 at 13:06
  • 8
    @akshay-chordiya I just checked, `Unit` in Kotlin does NOT work, `Void` does however. I assume there is a hardcoded check somewhere. – user3363866 Sep 04 '18 at 14:08
  • 2
    @user3363866 Using Unit in Kotlin worked for me, tested it 10 minutes ago. – omiwrench Feb 25 '21 at 09:52
  • when using Void object response will be null , instead of Void , Using Unit in Kotlin. response will not be null . – Ali Elahi May 23 '23 at 08:29
50

If you use RxJava, then it's better to use Completable in this case

Represents a deferred computation without any value but only indication for completion or exception. The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)?

http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html

in the accepted answer:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);

If the endpoint returns failure response code, it will still be in the onNext and you will have to check the response code yourself.

However, if you use Completable.

@GET("/path/to/get")
Completable getMyData(/* your args here */);

you will have only onComplete and onError. if the response code is success it will fire the onComplete else it will fire onError.

Ahmad Melegy
  • 1,530
  • 1
  • 16
  • 18
  • 3
    What will the `onError` `Throwable` argument contain, in that case? I find this cleaner, but we often still need to look at the response code and body for failures. – big_m Jun 17 '20 at 16:32
29

If you are using rxjava, use something like :

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
Geng Jiawen
  • 8,904
  • 3
  • 48
  • 37
15

With kotlin, using the return type Call<Void> still throws IllegalArgumentException: Unable to create converter for retrofit2.Call<java.lang.Void>

Using Response instead of Call resolved the issue

@DELETE("user/data")
suspend fun deleteUserData(): Response<Void>
pzulw
  • 1,716
  • 15
  • 22
2

Here is an example Kotlin in MVVM with service, Repository and ViewModel:

Service:

@POST("/logout")
suspend fun logout(@Header("Authorization") token: String):Response<Unit>

Repository:

    //logout
        private val mLogoutResponse = MutableLiveData<String>()
        val logoutResponse: LiveData<String>
            get() {
                return mLogoutResponse
            }
        suspend fun logout(token: String) {
            try {
                val result=quizzerProfileApi.logout(token)
                if(result.code()!=0)
                {
                    mLogoutResponse.postValue(result.code().toString())
                }
            } catch (e: Exception) {
                Log.d("ProfileRepository", "logout: Error: $e")
            }
        }

ViewModel:

fun logout(token: String) {
    viewModelScope.launch {
        repository.logout(token)
    }
}
val logoutResponseCd: LiveData<String>
        get() = repository.logoutResponse

in Activity:

 private fun logout() {
        myViewModel.logout(token)
        myViewModel.logoutResponseCd.observe(this, Observer {
            if(it!="0"){
                Log.d(TAG, "logout: code= $it")
                finish()
            }
            else
                Toast.makeText(this, "Error logging out: $it", Toast.LENGTH_SHORT).show()
        })
    }
Dharman
  • 30,962
  • 25
  • 85
  • 135
Rauson Ali
  • 111
  • 10
1

Kotlin

    @POST
    suspend fun accountVerification(
        @Body requestBody: RequestBody
    ): Response<Unit>

and success can be checked using

 if (response.isSuccessful) { }

this line is not needed if you are not bothered about the response code

MarGin
  • 2,078
  • 1
  • 17
  • 28
0

Here is how I used it with Rx2 and Retrofit2, with PUT REST request: My request had a json body but just http response code with empty body.

The Api client:

public class ApiClient {
public static final String TAG = ApiClient.class.getSimpleName();


private DevicesEndpoint apiEndpointInterface;

public DevicesEndpoint getApiService() {


    Gson gson = new GsonBuilder()
            .setLenient()
            .create();


    OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    okHttpClientBuilder.addInterceptor(logging);

    OkHttpClient okHttpClient = okHttpClientBuilder.build();

    apiEndpointInterface = new Retrofit.Builder()
            .baseUrl(ApiContract.DEVICES_REST_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(DevicesEndpoint.class);

    return apiEndpointInterface;

}

The interface:

public interface DevicesEndpoint {
 @Headers("Content-Type: application/json")
 @PUT(ApiContract.DEVICES_ENDPOINT)
 Observable<ResponseBody> sendDeviceDetails(@Body Device device);
}

Then to use it:

    private void sendDeviceId(Device device){

    ApiClient client = new ApiClient();
    DevicesEndpoint apiService = client.getApiService();
    Observable<ResponseBody> call = apiService.sendDeviceDetails(device);

    Log.i(TAG, "sendDeviceId: about to send device ID");
    call.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
        @Override
        public void onSubscribe(Disposable disposable) {
        }

        @Override
        public void onNext(ResponseBody body) {
            Log.i(TAG, "onNext");
        }

        @Override
        public void onError(Throwable t) {
            Log.e(TAG, "onError: ", t);

        }

        @Override
        public void onComplete() {
            Log.i(TAG, "onCompleted: sent device ID done");
        }
    });

}
Moti Bartov
  • 3,454
  • 33
  • 42
0

You can try this one

Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
                .addConverterFactory(new NullOnEmptyConverterFactory())
.client(okHttpClient).build();
 class NullOnEmptyConverterFactory extends Converter.Factory {
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
        return (Converter<ResponseBody, Object>) body -> {
            if (body.source().exhausted()) return null;
            return delegate.convert(body);
        };
    }
}