OkHttp is the HTTP client most Java, Kotlin, and Android code reaches for, and its proxy support is clean once you know that the proxy belongs to the client and authentication goes through a callback rather than a header. This guide covers proxies with OkHttp end to end: the proxy() builder method, authenticating with a proxyAuthenticator, SOCKS, rotating a pool with a ProxySelector, why the proxy is per client and what that means for design, timeouts, and verifying the exit IP.
We run a proxy network and a live proxy checker, so the OkHttp proxy mistake we see most is a Proxy-Authorization header set by hand on the request, which OkHttp does not carry through the CONNECT tunnel the way people expect. The fix is the proxyAuthenticator below. Every example is Java and translates directly to Kotlin. Where one needs a live proxy, pull a fresh one from our free proxy API, which returns recently checked endpoints with no key.
How do you set a proxy in OkHttp?
Build a Proxy and pass it to the client builder. Every request that client sends then goes through it:
import okhttp3.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.7", 8080)))
.build();
Request request = new Request.Builder().url("https://httpbin.org/ip").build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
The proxy is a property of the client, not the request. That is the single most important thing to internalise: to change the exit you change the client, or you install a selector that changes it for you.
Authentication goes through a callback
A paid proxy needs credentials, and this is where hand-written headers fail. OkHttp answers a proxy's 407 challenge through a proxyAuthenticator, which it calls with the failed response so you can return a request carrying the right header:
import okhttp3.Credentials;
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.7", 8080)))
.proxyAuthenticator((route, response) -> {
String credential = Credentials.basic("myuser", "mypass");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.build();
Credentials.basic builds the Basic auth value for you. The reason this works where a manual header does not is the CONNECT tunnel: for an HTTPS target, OkHttp authenticates to the proxy during the tunnel setup, and only the proxyAuthenticator path feeds credentials into that step.
Request via proxy
HTTPS needs a CONNECT tunnel
Proxy returns 407
authentication required
proxyAuthenticator fires
you add Proxy-Authorization
Tunnel opens
request proceeds
SOCKS proxies
For a SOCKS proxy, change the Proxy.Type to SOCKS:
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("203.0.113.7", 1080)))
.build();
One caveat carried over from the JVM: SOCKS support resolves the target hostname on your machine by default, so the DNS lookup does not travel to the proxy the way socks5h sends it. If proxy-side resolution matters for your job, either resolve through the proxy explicitly or use an HTTP proxy, which sidesteps the question. We cover the split in what is a SOCKS5 proxy.
Rotating a pool with a ProxySelector
Building a client per proxy works, but it throws away OkHttp's connection pool each time. To rotate through one reused client, install a ProxySelector that returns a different proxy per request:
import java.net.*;
import java.util.*;
class RotatingSelector extends ProxySelector {
private final List<Proxy> pool;
private final Random rnd = new Random();
RotatingSelector(List<Proxy> pool) { this.pool = pool; }
@Override public List<Proxy> select(URI uri) {
return List.of(pool.get(rnd.nextInt(pool.size())));
}
@Override public void connectFailed(URI uri, SocketAddress sa, java.io.IOException e) {
// a dead exit surfaces here; a real pool would retire it
}
}
List<Proxy> pool = List.of(
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.7", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.24", 3128))
);
OkHttpClient client = new OkHttpClient.Builder()
.proxySelector(new RotatingSelector(pool))
.build();
Pair it with call-level retries so a request that draws a dead exit is retried on another, because on any pool some exits are always down. You can fill the pool from our free proxy API, which returns a plain-text list you can parse into Proxy objects at startup.
Timeouts
An OkHttp client with no timeouts will wait a long time on a proxy that connects and then stalls. Set them on the builder:
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.7", 8080)))
.connectTimeout(java.time.Duration.ofSeconds(5))
.readTimeout(java.time.Duration.ofSeconds(20))
.callTimeout(java.time.Duration.ofSeconds(30))
.build();
connectTimeout bounds reaching the proxy, readTimeout bounds waiting for data once connected, and callTimeout caps the whole call including retries and redirects. For unattended scraping, set all three.
Verify the exit IP
Confirm the proxy took effect rather than trusting it. Fetch an IP echo through the proxied client and compare it to your own address:
Request check = new Request.Builder().url("https://httpbin.org/ip").build();
try (Response r = client.newCall(check).execute()) {
System.out.println("exit IP: " + r.body().string());
}
If the address is your own, the client you sent with has no proxy, or a system ProxySelector overrode it. For a fuller read of the exit, our proxy checker reports the exit IP, country, latency and anonymity grade in one paste.
Where to go from here
OkHttp's proxy model is two rules: the proxy lives on the client, and authentication goes through the proxyAuthenticator callback rather than a header. Once those are habit, the rest is the discipline every proxied client needs: verify each exit, bound your timeouts, and rotate a pool that is alive.
The Java proxy guide covers the java.net and HttpClient paths if part of your code is not on OkHttp, proxies for web scraping covers choosing the right proxy type, and how to fix 407 Proxy Authentication Required goes deeper on the auth error above. When a hand-rolled ProxySelector becomes a chore, a rotating gateway that returns a fresh residential IP per request at $0.44/GB lets one client keep a single endpoint while the exit changes underneath it.