Getting started with Retrofit
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 @GET
annotation 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.