Getting started with Retrofit

Monesh Garikina
Globant
Published in
5 min readMay 9, 2023
Photo by Dai KE on Unsplash

Retrofit is a type-safe HTTP client for Java and Android developed by Square. In this article, I will explain how to communicate with REST APIs using Retrofit and its features with the help of an example.

Why Retrofit?

Retrofit has the following features:

  • It provides a convenient builder to construct a Retrofit object, which is used to make HTTP calls to REST APIs.
  • It simplifies API representation through Java Interface.
  • We can use different Converter Factory classes to map the response data in different formats, like JSON, XML, etc., to the DTO class.
  • It allows making synchronous and asynchronous requests to APIs.
  • We can modify API requests through annotations in the Java interface.

Getting started with an example

In this example, we are going to create a simple Spring Boot application that can create a User and fetch the User information. We can also implement APIs to perform more operations on Users like updating and deleting the Users.

Let’s start by adding Retrofit and Gson converter dependencies in pom.xml. There are multiple converter dependencies for different response formats. Please refer here for more information.

<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-gson</artifactId>
<version>2.3.0</version>
</dependency>

API Modeling

Let’s create a User class, which will take the values from the response we receive from the API call. Retrofit won’t complain about extra properties (the properties present in the response received, not present in User class) for it takes only what we need. Even if we add properties in User class that are not present in JSON, no exception will be thrown.

package com.example.retrofitTest.dto;

import lombok.*;

@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {

private int userId;
private String username;
private String email;
}

Let’s move on to UserService Interface modeling and explore Retrofit annotations.

package com.example.retrofitTest.service;

import com.example.retrofitTest.dto.User;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;

import java.util.List;

public interface UserService {

@GET("user")
public Call<List<User>> getUsers();

@POST("user")
public Call<User> saveUser(@Body User user);
}

Here @GETannotation represents the HTTP method and the resource that needs to be called. If our base URL is http://localhost:8080/, then the request will be sent to http://localhost:8080/user. Similarly, we can use @POST, @PUT, @DELETE, etc., for the respective HTTP methods.

We also have other annotations like @Query, @Path, @Body for query parameters, path parameters, and request body. @Query is optional; if we pass a null value for @Query then that parameter will be ignored automatically while making the REST call.

Retrofit Object Creation

Let’s create a RetrofitServiceGenerator class. This class is used to construct the Retrofit object and define a createService method that takes the Java Interface with API representation as a parameter and returns the implementation of the class.

package com.example.retrofitTest.util;

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitServiceGenerator {

public static final String BASE_URL = "http://localhost:8080/";

private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());

private static Retrofit retrofit = builder.build();

private static OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();

public static <S> S createService(Class<S> serviceClass){
builder.client(httpClient.build());
retrofit = builder.build();
return retrofit.create(serviceClass);
}

}

Retrofit provides a convenient builder to create Retrofit objects. The baseUrl method accepts a URL as a parameter and this URL is going to be used whenever the service is called. GsonConverterFactory is used to convert JSON response into the type defined in Interface methods. We are using OkHttpClient to connect to the server and send and retrieve the information.

Synchronous/Asynchronous API

Now that we have RetrofitServiceGenerator class which has a Retrofit object, let’s call the API in synchronous and asynchronous ways.

package com.example.retrofitTest.controller;

import com.example.retrofitTest.dto.User;
import com.example.retrofitTest.service.UserService;
import com.example.retrofitTest.util.RetrofitServiceGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/users")
@Slf4j
public class UserController {

private UserService userService = RetrofitServiceGenerator.createService(UserService.class);

@GetMapping
public ResponseEntity<List<User>> getUsers(){
List<User> result = new ArrayList<>();
try {
Call<List<User>> users = userService.getUsers();
Response<List<User>> response = users.execute();
result = response.body();
}
catch (IOException e){
log.info("Exception occured",e);
}
return new ResponseEntity<List<User>>(result, HttpStatus.OK);
}

@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) throws IOException{
final User[] createdUser = {new User()};
Call<User> callAsync = userService.saveUser(user);
callAsync.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
createdUser[0] = response.body();
}

@Override
public void onFailure(Call<User> call, Throwable throwable) {
log.error("Exception",throwable);
}
});
return new ResponseEntity<User>(createdUser[0], HttpStatus.CREATED);
}
}

Here Retrofit takes care of the construction of the UserService interface by injecting the code necessary to make the request based on the annotations we have added to the interface.

Synchronous Request

The getUsers() method is an example of a Synchronous call. We are getting a Call<List<User>> object that is used to execute the request to the API. execute method is used to execute the REST call synchronously and blocks the current thread while transferring the data.

After the call is executed successfully, we receive a List<User> object thanks to the GsonConverterFactory which converts the JSON body into the respective response object.

Asynchronous Requests

The createUser method is an example of an asynchronous call. Here we are using the enqueue method that takes a CallBack<User> object as a parameter to retrieve the success and failure of the request. This will be executed in a separate thread. The response body can be retrieved in the same way as execute method.

With the help of Rx-Java, we can have different types like Observable, Flowable and Completable instead of Call<> adapters which are used to handle complex scenarios easily.

Authentication

We need authentication to access most APIs as they are secured. Let’s create a createService method in the RetrofitServiceGenerator class that takes the JWT token as a parameter.

public static <S> S createService(Class<S> serviceClass, String token){
if(token!=null) {
httpClient.interceptors().clear();
httpClient.addInterceptor(chain -> {
Request original = chain.request();
Request request = original.newBuilder()
.header("Authorization", token)
.build();
return chain.proceed(request);
});
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit.create(serviceClass);
}

We need to use the interceptor capabilities of OkHttpClient to add the token in the header.

Conclusion

Retrofit is a handy library as it is a type-safe HTTP Client. It provides a convenient builder, can make sync/async API calls, represents API through Java Interface, and provides annotation support. For more examples, you can refer here.

--

--