OkHttp (Java)

The previous lesson used HttpURLConnection to talk to a server and showed why network calls must run off the main thread. OkHttp is the HTTP client that most real Android apps reach for instead: it is efficient (connection pooling, transparent GZIP, response caching), has a clean request/response API, and is the foundation that libraries like Retrofit are built on top of.

In this lesson we will use OkHttp directly in our RecipeBox app: fetching the list of recipes from a server and submitting a newly created recipe.

Adding the Dependency

OkHttp is added through Gradle like any other library. In your module's build.gradle:

dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
}
Note: the logging-interceptor artifact is optional but useful during development – we will use it later in this lesson.

Building a GET Request

An OkHttpClient is the reusable object that actually executes requests. Create one instance and share it across your app instead of creating a new client per call.

// RecipeApi.java
public class RecipeApi {

    private final OkHttpClient client = new OkHttpClient();

    public void fetchRecipes(Callback callback) {
        Request request = new Request.Builder()
                .url("https://api.recipebox.example.com/recipes")
                .get()
                .build();

        client.newCall(request).enqueue(callback);
    }
}
Explanation:
  • Request.Builder() assembles the URL, HTTP method, headers, and body.
  • .get() is the default method, shown here for clarity.
  • client.newCall(request) creates a Call object representing this one execution.

Executing Asynchronously with enqueue()

OkHttp calls can run .execute() synchronously (blocking – never on the main thread) or .enqueue() asynchronously. For UI code, always use enqueue(): OkHttp automatically runs the request on a background thread pool for you, and delivers onResponse() / onFailure() back on that same background thread – not the main thread.

// Inside RecipeListActivity.java
api.fetchRecipes(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        runOnUiThread(() -> showError("Could not load recipes"));
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (!response.isSuccessful()) {
            runOnUiThread(() -> showError("Server error: " + response.code()));
            return;
        }
        String json = response.body().string();
        List<Recipe> recipes = parseRecipes(json);
        runOnUiThread(() -> adapter.setRecipes(recipes));
    }
});
Explanation:
  • Just like android-http-basics.php explained for HttpURLConnection, touching views from a background thread crashes the app – so every UI update here is wrapped in runOnUiThread().
  • response.body().string() reads the entire body into a String. It can only be read once.
  • response.isSuccessful() checks for a 2xx status code before trusting the body.

POST Request with a JSON Body

Submitting a new recipe means sending a JSON body along with the request:

// RecipeApi.java
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

public void createRecipe(Recipe recipe, Callback callback) {
    String json = new Gson().toJson(recipe);
    RequestBody body = RequestBody.create(json, JSON);

    Request request = new Request.Builder()
            .url("https://api.recipebox.example.com/recipes")
            .post(body)
            .build();

    client.newCall(request).enqueue(callback);
}
Explanation:
  • MediaType tells the server what format the body is in.
  • RequestBody.create(json, JSON) wraps the JSON string for the request.
  • The Callback passed to .post(body) works exactly the same as the GET example – still asynchronous, still requires runOnUiThread() for UI updates.

Logging Requests with an Interceptor

While developing, it helps to see the exact request and response OkHttp sends and receives – headers, body, timing. An interceptor lets you inspect (or modify) every call passing through the client. The HttpLoggingInterceptor is a ready-made one that prints everything to Logcat (see android-logging-logcat.php for reading Logcat output in general).

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(logging)
        .build();
Warning: Level.BODY logs full request/response bodies, which may include sensitive data. Only enable it in debug builds, never in a release build.

Timeouts

By default OkHttp uses a 10-second timeout for connecting, reading, and writing. You can tune these on the client builder – useful for slow networks or large recipe photo uploads:

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .build();

Where OkHttp Fits

Your Code

Builds Request, reads Response

→

OkHttpClient

Connection pool, threads, interceptors

→

Network

Server API

Working with raw Request/Response objects and manual JSON parsing is fine, but it gets repetitive across many endpoints. The next lesson introduces Retrofit, which builds a type-safe, interface-based layer directly on top of the OkHttp client you just learned.

Next up: Retrofit (Java) + Gson – define API endpoints as a Java interface and let Retrofit handle the request building and JSON conversion for you.