Retrofit (Java) + Gson
In android-okhttp.php we built requests manually with Request.Builder() and parsed JSON by hand. Retrofit is built directly on top of OkHttp, but instead of assembling requests yourself, you describe your API as a plain Java interface and Retrofit generates the networking code for you – type-safe, less error-prone, and far less boilerplate as an app grows past a couple of endpoints.
Adding the Dependency
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
}
Defining an API Interface
The core idea in Retrofit: instead of writing request code, you declare what each endpoint looks like using annotations, and Retrofit fills in the implementation at runtime.
// RecipeApiService.java
public interface RecipeApiService {
@GET("recipes")
Call<List<Recipe>> getRecipes();
}
- @GET("recipes") is appended to the base URL you configure below.
- Call<List<Recipe>> describes the response shape – a list of Recipe objects.
- Nothing here talks about HTTP directly; Retrofit generates the real implementation of this interface.
Building the Retrofit Instance
A Retrofit instance needs a base URL and a converter factory that turns raw JSON into Java objects (and back). We use Gson here – it has long been the traditional choice for Java Android projects because it needs no extra annotations to map JSON keys to matching Java field names, and it integrates directly with Retrofit through converter-gson.
// ApiClient.java
public class ApiClient {
private static Retrofit retrofit;
public static RecipeApiService getRecipeApi() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl("https://api.recipebox.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit.create(RecipeApiService.class);
}
}
- baseUrl() must end with a trailing slash.
- retrofit.create(RecipeApiService.class) returns a generated implementation of the interface.
- Internally, Retrofit uses its own OkHttpClient to actually send the requests.
Calling the API and Handling the Callback
// Inside RecipeListActivity.java
RecipeApiService api = ApiClient.getRecipeApi();
Call<List<Recipe>> call = api.getRecipes();
call.enqueue(new Callback<List<Recipe>>() {
@Override
public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) {
if (response.isSuccessful() && response.body() != null) {
List<Recipe> recipes = response.body();
adapter.setRecipes(recipes);
} else {
showError("Server error: " + response.code());
}
}
@Override
public void onFailure(Call<List<Recipe>> call, Throwable t) {
showError("Network failure: " + t.getMessage());
}
});
- response.body() is already a parsed List<Recipe> – Gson did the JSON conversion for you.
- Important: Retrofit delivers onResponse()/onFailure() on the main thread when you use the default Android executor callback adapter – but this is Retrofit's own convenience, not something Java gives you automatically. Many teams still call runOnUiThread() explicitly to be safe, and it is required if you configure a custom CallAdapter.
- Compared to raw OkHttp, this is more structured – but it is still a callback, not a coroutine or a suspend function; Java has no built-in equivalent to Kotlin's coroutines, so this pattern is what you will use throughout the Java track.
@GET, @POST, @Path, @Query, @Body
| Annotation | Purpose | Example |
|---|---|---|
| @GET | Read data | @GET("recipes") |
| @POST | Create data | @POST("recipes") |
| @Path | Replace a placeholder in the URL | @GET("recipes/{id}") |
| @Query | Add a URL query parameter | ?category=dessert |
| @Body | Send an object as the JSON request body | a full Recipe object |
// RecipeApiService.java
public interface RecipeApiService {
@GET("recipes")
Call<List<Recipe>> getRecipes();
@GET("recipes/{id}")
Call<Recipe> getRecipeById(@Path("id") String recipeId);
@GET("recipes")
Call<List<Recipe>> getRecipesByCategory(@Query("category") String category);
@POST("recipes")
Call<Recipe> createRecipe(@Body Recipe recipe);
}
- getRecipeById("r42") requests recipes/r42.
- getRecipesByCategory("dessert") requests recipes?category=dessert.
- createRecipe(recipe) serializes the Recipe object to JSON automatically via the Gson converter.
Mapping Fields with @SerializedName
Gson maps JSON keys to Java fields by matching names exactly. When the server's naming doesn't match your Java field names – for example, a REST API using snake_case while your Recipe class uses camelCase – annotate the field with @SerializedName:
// Recipe.java
public class Recipe {
private String id;
private String title;
private List<String> ingredients;
private List<String> steps;
@SerializedName("photo_uri")
private String photoUri;
private String category;
@SerializedName("prep_minutes")
private int prepMinutes;
// getters and setters omitted for brevity
}