OkHttp takes a proxy in one of two ways. You set it on the client with proxy(), or OkHttp asks a ProxySelector each time it opens a new connection. For an https:// site it opens a CONNECT tunnel through an HTTP proxy and answers the proxy's login challenges through a proxyAuthenticator. A SOCKS proxy goes to Java's own socket.
This guide follows OkHttp's source, documentation and change log up to version 5.5.0 of August 16, 2026, read on October 13, 2026, together with Oracle's Java networking properties. None of the code was run for this page. One note for anyone looking up the source: github.com/square/okhttp now redirects to github.com/lysine-dev/okhttp.
Setting a proxy on the client
OkHttp's builder takes a java.net.Proxy, Java's own class, with the proxy's type and address.
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.7", 8080)))
.build();
The same in Kotlin:
val client = OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("203.0.113.7", 8080)))
.build()
OkHttp's own documentation describes proxy() this way: it "Sets the HTTP proxy that will be used by connections created by this client." A selector is the fallback, "which is only honored when this proxy is null". And Proxy.NO_PROXY switches proxies off: "To disable proxy use completely, call" proxy(Proxy.NO_PROXY).
For calls that need another proxy, derive a second client with newBuilder(). In OkHttp's words, that client "shares the same connection pool, thread pools, and configuration", so a client per proxy costs little. For Java's own HttpClient and HttpURLConnection, see our Java proxy guide.
When you set no proxy: the ProxySelector
With proxy() left unset, OkHttp asks a ProxySelector, and without one of yours it uses Java's system-wide default. Its documentation adds that a selector "may return multiple proxies; in that case they will be tried in sequence until" one connects.
Java's default selector reads a few properties. Oracle lists http.proxyHost, https.proxyHost and socksProxyHost, each with no default. It takes the operating system's own proxy settings only with java.net.useSystemProxies, which defaults to false, and "Note that this property is checked only once at startup." On Android, the system settings carry their own warning about a Wi-Fi proxy: "The HTTP proxy is used by the browser but may not be used by the other apps." Set the proxy on the client when it has to apply.
Two changes in OkHttp 5 concern the selector. Version 5.0.0-alpha.15 of May 2025 began calling ProxySelector.connectFailed() when a connection's first TCP handshake fails. And version 5.5.0 notes: "Fix: Fall back to no proxy when the system proxy selector throws." Before that, the call crashed.
How OkHttp reaches an https:// site through an HTTP proxy
Pick the proxy
proxy(), else the selector, else Java's default
Ask the authenticator
a stand-in 407 named OkHttp-Preemptive
Send CONNECT
only the host, none of your headers
Proxy answers 407
proxyAuthenticator again; at most 21 tries
Proxy answers 200
TLS to the site runs inside the tunnel
OkHttp's connection code states the plan in one line: "To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT request to create" the tunnel. OkHttp writes that request itself, with the site's host and port. Its documentation explains what the CONNECT leaves out: it "doesn't include the motivating request's HTTP headers or even its full URL". It also skips your interceptors and event listeners.
That is why a Proxy-Authorization header added to your own request does nothing for an https:// site. The proxy only ever sees the CONNECT, and the login has to come from the authenticator.
Logging in to an HTTP proxy
The proxyAuthenticator answers the proxy's challenges. OkHttp's documentation names its job: it "Sets the authenticator used to respond to challenges from proxy servers." This one covers both cases below.
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("203.0.113.7", 8080)))
.proxyAuthenticator((route, response) -> {
if (response.request().header("Proxy-Authorization") != null) {
return null; // these credentials already failed
}
String credential = Credentials.basic("myuser", "mypass");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.build();
OkHttp calls it twice over. "Prior to sending any CONNECT request OkHttp always calls the proxy authenticator so that it may" prepare a login in advance. For that call it passes a stand-in 407 response with the challenge OkHttp-Preemptive, so the code above sends the credentials with the first CONNECT. Then, "When reactive authentication is requested by a proxy server, the response code is 407", and the authenticator is asked again. RFC 9110 defines the 407 as the proxy telling the client to "authenticate itself in order to use a proxy for this request".
The check at the top keeps a wrong password from looping. OkHttp's own example does the same with "return null; // Give up, we've already failed to authenticate." When the authenticator returns null after a real 407, OkHttp throws "Failed to authenticate with proxy". Any other answer than 200 or 407 throws "Unexpected response code for CONNECT" with the code. And the tunnel code allows MAX_TUNNEL_ATTEMPTS = 21 before it gives up with "Too many tunnel connections attempted". Our guide to 407 Proxy Authentication Required covers the login side, and the page on ERR_TUNNEL_CONNECTION_FAILED reads the proxy's other answers.
SOCKS proxies
For SOCKS, change the type. The login then goes through Java itself.
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("203.0.113.7", 1080)))
.build();
// A SOCKS5 login, for the whole JVM:
java.net.Authenticator.setDefault(new java.net.Authenticator() {
@Override protected java.net.PasswordAuthentication getPasswordAuthentication() {
return new java.net.PasswordAuthentication("myuser", "mypass".toCharArray());
}
});
Two lines in OkHttp's source explain the difference. For a SOCKS route it builds the target with InetSocketAddress.createUnresolved(socketHost, socketPort). The hostname reaches the proxy unresolved, so the proxy looks it up. And it opens the connection with JavaNetSocket(route.proxy), a Java socket that speaks SOCKS itself, where HTTP and direct routes use the client's socket factory.
So the SOCKS login follows Java's rules. Oracle's properties set socksProxyVersion to 5 by default, and java.net.socks.username is the "Username to use if the SOCKSv5 server asks for authentication" when no java.net.Authenticator is set. OkHttp's proxyAuthenticator answers the HTTP proxy's 407 challenges.
Who talks to the proxy, by Proxy.Type
| Type | Connects | Login |
|---|---|---|
| HTTP | OkHttp | Its callback |
| SOCKS | Java's socket | Java settings |
| DIRECT | OkHttp | None needed |
OkHttp source: ConnectPlan.kt and RouteSelector.kt; Oracle, Java Networking Properties; read October 13, 2026
Spreading calls over several proxies
OkHttp chooses a proxy when it opens a connection and then reuses that connection. Its connections document puts the order plainly. First OkHttp looks in the connection pool, and only "If it doesn't find a connection in the pool" does it plan a route: "It then selects a TLS version and proxy server if necessary." After a call, "the connection will be returned to the pool so it can be reused for a future request."
So a ProxySelector that returns a different proxy each time changes only the connections opened after it. Two set-ups behave predictably. A rotating gateway keeps one proxy address in OkHttp while the outgoing address changes behind it. Or build one client per proxy with newBuilder() and pick the client per call. Use addresses you are allowed to use, and keep each site's own rules: its terms and its robots.txt apply whatever the address.
Timeouts
OkHttp's defaults are in its source: connect, read and write each default to 10 seconds, and the call timeout is "The default value is 0 which imposes no timeout." The CONNECT exchange uses the read and write timeouts, so a proxy that accepts the connection and then stalls hits the read timeout.
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();
A call timeout bounds the whole call, retries and redirects included, which suits unattended jobs.
Check the exit
Send one call through the proxied client to a page that shows your address, and compare it with the address you see without the proxy. Our proxy checker tests a proxy from your own connection before you build a client around it, and our free proxy API returns recently checked addresses with no key, enough for a first test.
What other guides get wrong
- ZenRows writes that "OkHttp provides a Proxy class that offers a way to represent a proxy server." The class is
java.net.Proxy, Java's own, which OkHttp's builder takes. The same page sells the proxy as a way to "scrape undetected". - Decodo says "proxyAuthenticator adds the credentials automatically and retries the request." That describes the reactive path. OkHttp also asks the authenticator before every CONNECT, with the
OkHttp-Preemptivechallenge. - Scrapfly's guide to OkHttp shows no
proxy()orproxyAuthenticatorsetup and sends readers to "Anti-bot bypass". - ScrapingBee's Kotlin guide shows no OkHttp proxy setup and points to "A guide to Web Scraping without getting blocked".
- MarsProxies sells rotation for "rotate identities, detect soft blocks" and "detection evasion". A proxy changes the address a request comes from, and each site's rules stay the same.
Where HProxy fits
Our residential proxies give a fresh IP per request from $0.44 per GB, behind one gateway address, which suits the rotating set-up above. Premium holds one address for up to 24 hours from $1.00 per GB, and it takes an IP whitelist, which spares the login. Our datacenter IPv4 proxies, from $1.58 per IP, speak HTTP, HTTPS and SOCKS5. With OkHttp, set our HTTP endpoint with proxy() and the login in a proxyAuthenticator; for SOCKS5, the login goes through Java's settings as shown above.
What we could not find out
- How Android's own selector treats a Wi-Fi proxy for OkHttp. We read Android's settings text only.
- Whether some SOCKS proxies refuse an unresolved hostname. OkHttp sends one; we ran no code and used no proxy.
- Why OkHttp's repository moved. The project's pages we read give no reason.
Sources
Read on October 13, 2026.
- OkHttp source on GitHub: OkHttpClient.kt, Authenticator.kt, ConnectPlan.kt and RouteSelector.kt.
- OkHttp documentation: Connections, Recipes and the change log up to 5.5.0.
- Oracle: Java Networking Properties, Java SE 21.
- RFC 9110, HTTP Semantics, section 15.5.8 (407).
- Android Open Source Project: strings of the Settings app, for the Wi-Fi proxy warning.
- Search demand: Google Keyword Planner, and Search Console for this page, July 15 to October 12, 2026.
- Guides we judged, read the same day and deliberately left unlinked: ZenRows, Decodo, Scrapfly, ScrapingBee and MarsProxies.



