Daniel Dallos A solution-oriented pragmatic creator. Android and IoT enthusiast. iOS developer. Technology lover. Find me online at danieldallos.com.

A complete guide to OkHttp

9 min read 2551

Symbol of a Concentric Square

What is OkHttp?

OkHttp is an HTTP client from Square for Java and Android applications. It’s designed to load resources faster and save bandwidth. OkHttp is widely used in open-source projects and is the backbone of libraries like Retrofit, Picasso, and many others.

Here are the key advantages to using OkHttp:

  • HTTP/2 support (efficient socket usage)
  • Connection pooling (reduces request latency in the absence of HTTP/2)
  • GZIP compression (shrinks download sizes)
  • Response caching (avoids re-fetching the same data)
  • Silent recovery from common connection problems
  • Alternative IP address detection (in IPv4 and IPv6 environments)
  • Support for modern TLS features (TLS 1.3, ALPN, certificate pinning)
  • Synchronous and asynchronous call support

In this guide, we’ll cover the basics of OkHttp by building an imaginary to-do list application for Android.

First, let’s define some functional requirements for our to-do list app. Our users will want to be able to see their saved to-dos from the to-do server, save a new to-do on the server, and securely and solely access their own to-dos.

As developers, we want to be able to easily debug the network communication of our app and reduce the load on the server side.

Prerequisites

The stable OkHttp 4.x works on Android 5.0+ (API level 21+) and Java 8+. If you require lower Android and Java version support, you can still rely on OkHttp 3.12.x branch with some considerations.

When importing OkHttp, it will also bring two dependencies: Okio, a high-performance I/O library, and the Kotlin Standard library. You don’t have to import these separately.

To use OkHttp in your Android project, you need to import it in the application-level Gradle file:

implementation("com.squareup.okhttp3:okhttp:4.9.1")

Don’t forget that on Android, you need to request the INTERNET permission in the AndroidManifest.xml file of your application if you would like to access network resources:

<uses-permission android:name="android.permission.INTERNET"/>

Setting up OkHttp

In order for our users to see all of their saved to-dos from the server, we’ll need synchronous and asynchronous GET requests, as well as query parameters.

We made a custom demo for .
No really. Click here to check it out.

GET requests

To get our to-do list from the server, we need to execute a GET HTTP request. OkHttp provides a nice API via Request.Builder to build requests.

Synchronous GET

Making a GET request is as easy as this:

OkHttpClient client = new OkHttpClient();

Request getRequest = new Request.Builder()
        .url("https://mytodoserver.com/todolist")
        .build();

try {
    Response response = client.newCall(getRequest).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

As you can see, this is a synchronous way to execute the request with OkHttp. (You should run this on a non-UI thread, otherwise, you will have performance issues within your application and Android will throw an error.)

Asynchronous GET

The asynchronous version of this request provides you with a callback when the response was fetched or an error occurred.

OkHttpClient client = new OkHttpClient();

Request getRequest = new Request.Builder()
        .url("https://mytodoserver.com/todolist")
        .build();

client.newCall(getRequest).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) {
        e.printStackTrace();
    }

    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
        System.out.println(response.body().string());
    }
});

Note: From now on, I will only show the synchronous version of the calls to avoid using tons of boilerplate code. I also try to use standard Java APIs whenever it is possible to make the code reusable in non-Android environments.

Query parameters in OkHttp

You can pass query parameters to your request, such as implementing filtering on the server-side for completed or incomplete to-dos.

OkHttpClient client = new OkHttpClient();

HttpUrl.Builder queryUrlBuilder = HttpUrl.get("https://mytodoserver.com/todolist").newBuilder();
queryUrlBuilder.addQueryParameter("filter", "done");

Request request = new Request.Builder()
        .url(queryUrlBuilder.build())
        .build();

try {
    Response response = client.newCall(request).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

HttpUrl.Builder will generate the proper URL with query parameter: https://mytodoserver.com/todolist?filter=done.

You may rightfully ask, “Why not just use the manually created URL itself?” You could. But once your URL building logic gets more complicated (more query parameters), then this class comes in handy. The developers of the library have additional reasons to use HttpUrl.

POST request

Now we have all the to-dos downloaded from our server. But how can create new to-dos or mark one as done? With a simple POST request.

Simple POST request

Let’s send POST requests to our endpoint:

OkHttpClient client = new OkHttpClient();

RequestBody requestBody = new FormBody.Builder()
        .add("new", "This is my new TODO")
        .build();

Request postRequest = new Request.Builder()
        .url("https://mytodoserver.com/new")
        .post(requestBody)
        .build();

try {
    Response response = client.newCall(postRequest).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

As you can see, the body of the POST request is an application/x-www-form-urlencoded key-value pair data. But we can send any type we want. Here is an example with a JSON body:

OkHttpClient client = new OkHttpClient();

JSONObject jsonObject = new JSONObject();
jsonObject.put("todo_id", 123);
jsonObject.put("status", "done");

RequestBody requestJsonBody = RequestBody.create(
        jsonObject.toString(),
        MediaType.parse("application/json")
);

Request postRequest = new Request.Builder()
        .url("https://mytodoserver.com/modify")
        .post(requestJsonBody)
        .build();

try {
    Response response = client.newCall(postRequest).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

File upload

It’s also possible that we would like to attach a file (such as an image) to our new to-do:

OkHttpClient client = new OkHttpClient();

RequestBody requestBody = new MultipartBody.Builder()
        .addFormDataPart("new", "This is my new TODO")
        .addFormDataPart("image", "attachment.png",
                RequestBody.create(new File("path/of/attachment.png"), MediaType.parse("image/png"))
        )
        .setType(MultipartBody.FORM)
        .build();

Request postRequest = new Request.Builder()
        .url("https://mytodoserver.com/new")
        .post(requestBody)
        .build();

try {
    Response response = client.newCall(postRequest).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

Similar to before, we execute a multipart HTTP request where we can attach the desired file(s).

Canceling a request

It’s possible to accidentally choose the wrong attachment when saving a to-do, so instead of waiting until the upload finishes, ensure the request can be cancelled any time and restarted with the right value later.

// same request as before
Request postRequest = new Request.Builder()
        .url("https://mytodoserver.com/new")
        .post(requestBody)
        .build();

Call cancelableCall = client.newCall(postRequest);

try {
    Response response = cancelableCall.execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

// ... few seconds later from an other thread
cancelableCall.cancel();

Now we have all the knowledge necessary for basic functionality in our app. We can check our to-do list, we can add new ones, and we can change their state.

Let’s look at the security side of our application.

Security and authorization in OkHttp

Setting an HTTP header on a request

Our backend had implemented a basic username/password-based authentication to avoid seeing and modifying each other’s to-dos.

Accessing our data now requires an Authorization header to be set on our requests. Without that, the request could fail with a 401 Unauthorized response.

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
        .url("https://mytodoserver.com/todolist")
        .addHeader("Authorization", Credentials.basic("username", "password"))
        .build();

try {
    Response response = client.newCall(request).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

The addHeader() method on the Request.Builder will let us specify as many custom headers as we want.

Now our sensitive data is only accessible if someone knows our username and password. But what if someone is listening on the network and tries to hijack our requests with a man-in-the-middle attack and fabricated certificates?

OkHttp gives you an easy way to trust only your own certificate by using certificate pinner.

Setting up certificate pinner in OkHttp

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.certificatePinner(
        new CertificatePinner.Builder().add(
                "mytodoserver.com","sha256/public_key_hash_of_my_certification"
        ).build()
);

OkHttpClient client = clientBuilder.build();

Here we use OkHttpClient.Builder to build a custom OkHttp client (more on this later). Then, with CertificatePinner, we choose which certificates for which specific domains are trusted.

For more information on certificate pinning and security in general, please visit the relevant OkHttp documentation page.

Debugging with OkHttp

If an issue occurs while making a request, we have to dig deeper into why it happened. OkHttp has its own internal APIs to enable debug logging, which can help. But we can also leverage on OkHttp’s interceptor API to make our life easier.

Interceptor

Interceptors can monitor, rewrite, and retry calls. We can use them to modify a request before it goes out, pre-process a response before it reaches our logic, or simply print out some details about the requests.

OkHttp has its own pre-made logging interceptor that we can just import via Gradle:

implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")

And to use it:

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(loggingInterceptor)
        .build();

Or we can implement our own custom interceptor:

static class BasicLoggingInterceptor implements Interceptor {
    @NotNull
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();

        System.out.println(String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        System.out.println(String.format("Received response for %s %n%s",
                response.request().url(), response.headers()));

        return response;
    }
}

// ...
// usage later on
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new BasicLoggingInterceptor())
        .build();

We can also declare our interceptors on application and network-level too based on our needs. You can read more about this here.

Proxy

Sometimes it is useful to manipulate the responses of our backend API. We can achieve this by manipulating the server-side code, but it is more efficient via a proxy server.

We can use a system-wide proxy configuration on the device itself or instruct our OkHttp client to use one internally.

Proxy proxyServerOnLocalNetwork = new Proxy(
        Proxy.Type.HTTP,
        new InetSocketAddress("192.168.1.100", 8080) // the local proxy
);

OkHttpClient client = new OkHttpClient.Builder()
        .proxy(proxyServerOnLocalNetwork)
        .build();

Caching in OkHttp

After we have debugged our application, you may have noticed that we complete a lot of unnecessary requests that put extra load on our server. There is no need to fetch the to-do list again if there was no change on the backend.

There is a default cache implementation in OkHttp where we only need to specify the cache location and its size, like so:

OkHttpClient client = new OkHttpClient.Builder()
        .cache(new Cache(new File("/local/cacheDirectory"), 10 * 1024 * 1024)) //10 MB
        .build();

But you can get wild with it if you would like to customize the behavior.

If you have custom caching logic, you could also implement your own way of caching. For example, you could execute a HEAD request first to your server, then check the cache indication headers, and, if there was a change, execute a GET request to the same URL to fetch the content.

OkHttp configuration

We’ve already covered some usage of OkHttpClient.Builder. This class is useful if we would like to alter the default OkHttp client behavior.

There are some parameters worth mentioning:

OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache) // configure cache, see above
        .proxy(proxy) // configure proxy, see above
        .certificatePinner(certificatePinner) // certificate pinning, see above
        .addInterceptor(interceptor) // app level interceptor, see above
        .addNetworkInterceptor(interceptor) // network level interceptor, see above
        .authenticator(authenticator) // authenticator for requests (it supports similar use-cases as "Authorization header" earlier
        .callTimeout(10000) // default timeout for complete calls
        .readTimeout(10000) // default read timeout for new connections
        .writeTimeout(10000) // default write timeout for new connections
        .dns(dns) // DNS service used to lookup IP addresses for hostnames
        .followRedirects(true) // follow requests redirects
        .followSslRedirects(true) // follow HTTP tp HTTPS redirects
        .connectionPool(connectionPool) // connection pool used to recycle HTTP and HTTPS connections
        .retryOnConnectionFailure(true) // retry or not when a connectivity problem is encountered
        .cookieJar(cookieJar) // cookie manager
        .dispatcher(dispatcher) // dispatcher used to set policy and execute asynchronous requests
        .build();

For the complete list, please visit the documentation.

WebSocket

Thinking about a collaborative to-do list? Or notifying users once a new to-do is added? How about having a real-time chat over a to-do item? OkHttp’s got you covered here, too.

If you are done with the WebSocket server-side implementation, you can connect to that endpoint and get real-time messaging up and running from an OkHttp client.

OkHttpClient client = new OkHttpClient();

String socketServerUrl = "ws://mytodoserver.com/realtime";
Request request = new Request.Builder().url(socketServerUrl).build();

// connecting to a socket and receiving messages
client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
        super.onClosed(webSocket, code, reason);
        //TODO: implement your own event handling
    }

    @Override
    public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
        super.onClosing(webSocket, code, reason);
        //TODO: implement your own event handling
    }

    @Override
    public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
        super.onFailure(webSocket, t, response);
        //TODO: implement your own event handling
    }

    @Override
    public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
        super.onMessage(webSocket, text);
        //TODO: implement your own event handling for incoming messages
    }

    @Override
    public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
        super.onMessage(webSocket, bytes);
        //TODO: implement your own event handling for incoming messages
    }

    @Override
    public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
        super.onOpen(webSocket, response);
        //TODO: implement your own event handling
    }
});

// sending message
webSocket.send("new_todo_added");

Testing

We can’t forget about testing. OkHttp delivers its own MockWebServer to help test HTTP and HTTPS network calls. It lets us specify which response to return to which request and verifies every part of that request.

To start, we need to import it via Gradle:

testImplementation("com.squareup.okhttp3:mockwebserver:4.9.1")

Here are some important APIs:

  • MockWebServer.start(): starts the mock web server on the local host
  • MockWebServer.enqueue(mockResponse): queues a MockResponse. This is a FIFO queue that ensures the requests will receive responses in order as there were queued
  • MockResponse: a scriptable OkHttp response
  • RecordRequest: an HTTP request that was received by the MockWebServer
  • MockWebServer.takeRequest(): takes the next request arrived to the MockWebServer

Once we understand the basics we can write our first test. Now, for a basic GET request:

public class MockWebServerTest {
    final MockWebServer server = new MockWebServer();
    final OkHttpClient client = new OkHttpClient();

    @Test
    public void getRequest_Test() throws Exception {
        final String jsonBody = "{'todo_id': '1'}";
        // configure a MockResponse for the first request
        server.enqueue(
                new MockResponse()
                        .setBody(jsonBody)
                        .addHeader("Content-Type", "application/json")
        );

        // start the MockWebServer
        server.start();

        // create a request targeting the MockWebServer
        Request request = new Request.Builder()
                .url(server.url("/"))
                .header("User-Agent", "MockWebServerTest")
                .build();

        // make the request with OkHttp
        Call call = client.newCall(request);
        Response response = call.execute();

        // verify response
        assertEquals(200, response.code());
        assertTrue(response.isSuccessful());
        assertEquals("application/json", response.header("Content-Type"));
        assertEquals(jsonBody, response.body().string());

        // verify the incoming request on the server-side
        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("GET", recordedRequest.getMethod());
        assertEquals("MockWebServerTest", recordedRequest.getHeader("User-Agent"));
        assertEquals(server.url("/"), recordedRequest.getRequestUrl());
    }
}

Conclusion

In short, OkHttp is a powerful library that offers plenty of perks, including HTTP/2 support, recovery mechanism from connection problems, caching, and modern TLS support.

If you have ever tried to implement these functionalities from scratch via the default Android and Java network APIs, you know how much work and pain it is (and how many edge cases that you forgot to cover). Luckily, implementing networking in your application with OkHttp makes this easy.

For more details, please visit the project page and GitHub. You can find some useful extensions, implementation samples, and testing examples.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Daniel Dallos A solution-oriented pragmatic creator. Android and IoT enthusiast. iOS developer. Technology lover. Find me online at danieldallos.com.

Leave a Reply