Integration · HTTP clients
How to use a proxy with Java HttpClient
Java's java.net.http.HttpClient takes a proxy through ProxySelector.of(new InetSocketAddress(host, port)) and the credentials through an Authenticator. For HTTPS sites there is one more step: the JDK disables Basic authentication on CONNECT tunnels by default, so set jdk.http.auth.tunneling.disabledSchemes to an empty value, or every HTTPS request ends in a 407.
Reviewed 30 Oct 2026
http://USERNAME _pool-arcfast _country-de:PASSWORD@ proxy.arcproxies.io:8000The short answer
- Start the JVM with
-Djdk.http.auth.tunneling.disabledSchemes=; the JDK default isBasic, which blocks Basic proxy credentials on CONNECT tunnels. ProxySelector.of(...)plus anAuthenticatorthat answersRequestorType.PROXY.- One client keeps its tunnels: use one
HttpClientper sticky session, not one per request. - HttpClient ignores SOCKS proxies and connects directly. Use the gateway's HTTP protocol.
Setup
Set up Java HttpClient with a proxy, step by step
- 01
Allow Basic authentication on CONNECT tunnels
The JDK's
net.propertiesships withjdk.http.auth.tunneling.disabledSchemes=Basic. Override it on the command line, or withSystem.setPropertyas the first line ofmain, before any client sends a request. Without it, HTTPS requests return status 407 with no exception.shell4 linesjava -Djdk.http.auth.tunneling.disabledSchemes= -jar crawler.jar# or, first line of main():# System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); - 02
Build a client and check the exit IP
The authenticator answers the gateway's 407 challenge with the ARC username.
connectTimeoutbounds new connections; the requesttimeoutbounds the wait for the response headers.ExitIp.java35 linesimport java.net.Authenticator;import java.net.InetSocketAddress;import java.net.PasswordAuthentication;import java.net.ProxySelector;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.time.Duration;public class ExitIp { static HttpClient arcClient(String username) { return HttpClient.newBuilder() .proxy(ProxySelector.of(new InetSocketAddress("proxy.arcproxies.io", 8000))) .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() != RequestorType.PROXY) return null; return new PasswordAuthentication(username, "PASSWORD".toCharArray()); } }) .connectTimeout(Duration.ofSeconds(10)) .build(); } public static void main(String[] args) throws Exception { System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); HttpClient client = arcClient("USERNAME_pool-arcfast_country-de"); HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.ipify.org?format=json")) .timeout(Duration.ofSeconds(30)) .build(); HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.statusCode() + " " + res.body()); }} - 03
One client per sticky session
The username lives in the client's authenticator, and the client reuses its tunnel to a host. So a session is a client: mint a handle, build a client with the sticky username, use it for the task. On Java 21 and later,
HttpClientisAutoCloseable.inside main()13 lines// uses arcClient(...) from ExitIp.javaString handle = java.util.HexFormat.of().formatHex(java.security.SecureRandom.getSeed(4));String user = "USERNAME_pool-arcfast_country-de_session-" + handle + "_sesstime-10";try (HttpClient session = ExitIp.arcClient(user)) { for (int page = 1; page <= 3; page++) { HttpRequest req = HttpRequest.newBuilder( URI.create("https://example.com/search?q=lamp&page=" + page)) .timeout(Duration.ofSeconds(30)) .build(); System.out.println(page + " " + session.send(req, HttpResponse.BodyHandlers.discarding()).statusCode()); }} - 04
Target a country or city
A market is a username, so a market is a client too. Cities come from the published list, lowercased without spaces.
inside main()12 linesMap<String, String> markets = Map.of( "de-frankfurt", "USERNAME_pool-arcfast_country-de_city-frankfurt", "jp-nagoya", "USERNAME_pool-arcfast_country-jp_city-nagoya", "pl-warsaw", "USERNAME_pool-arcfast_country-pl_city-warsaw");for (var m : markets.entrySet()) { try (HttpClient c = ExitIp.arcClient(m.getValue())) { HttpRequest req = HttpRequest.newBuilder(URI.create("https://example.com/price/123")) .timeout(Duration.ofSeconds(30)).build(); System.out.println(m.getKey() + " " + c.send(req, HttpResponse.BodyHandlers.discarding()).statusCode()); }} - 05
Handle 407, timeouts and retries
With a wrong password the authenticator is asked again until the retry limit (3 by default), then
sendthrows anIOExceptionsaying "too many authentication attempts". Timeouts raiseHttpTimeoutException(connect:HttpConnectTimeoutException). HttpClient does not retry 429 or 5xx for you.a static method next to main()19 linesstatic HttpResponse<String> fetch(String url) throws Exception { for (int attempt = 0; attempt < 4; attempt++) { String handle = java.util.HexFormat.of().formatHex(java.security.SecureRandom.getSeed(4)); try (HttpClient c = ExitIp.arcClient("USERNAME_pool-arcfast_country-de_session-" + handle + "_sesstime-5")) { HttpRequest req = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(30)).build(); HttpResponse<String> res = c.send(req, HttpResponse.BodyHandlers.ofString()); if (res.statusCode() == 407) { throw new IllegalStateException("407: check disabledSchemes and the credentials"); } if (res.statusCode() != 429 && res.statusCode() < 500) return res; } catch (java.net.http.HttpTimeoutException e) { // new exit on the next attempt } catch (java.io.IOException e) { if (String.valueOf(e.getMessage()).contains("authentication attempts")) throw e; } Thread.sleep(1000L << attempt); // 1 s, 2 s, 4 s, 8 s } throw new IllegalStateException("gave up on " + url);}
Why HTTPS requests return 407 in Java
For an https:// URL the client first asks the gateway to open an HTTP CONNECT tunnel. The JDK refuses to send Basic credentials on that CONNECT request unless jdk.http.auth.tunneling.disabledSchemes is changed; the shipped net.properties sets it to Basic. The proxy keeps answering 407, and HttpClient hands that 407 back as the response.
Plain http:// URLs are not affected: the credentials travel on the proxied request itself, controlled by the separate jdk.http.auth.proxying.disabledSchemes, which is empty by default.
Rotating vs sticky with HttpClient
- Rotating: a username without
_session-. One client reuses its tunnel to a host, so consecutive requests usually share an exit; a new exit needs a new connection, which in practice means a new client or an idle connection that has timed out (30 seconds by default,jdk.httpclient.keepalive.timeout). - Sticky: a
_session-<handle>_sesstime-<minutes>username in a dedicated client. The handle keeps the exit even if the tunnel is reopened. - Per-request credentials do not work: a
Proxy-Authorizationheader with another username on a request was ignored for a tunnel that was already open in our test. Keep the username in the client.
The trade-offs are in rotating vs sticky sessions; to confirm where an exit really is, use the location check guide.
SOCKS5 and HttpClient
java.net.http.HttpClient does not use SOCKS proxies: a ProxySelector that returns a SOCKS proxy is ignored and the request goes out directly (OpenJDK issue JDK-8214516). That is a silent leak of your own IP, so use the gateway as an HTTP proxy from Java. HttpURLConnection and third-party clients do support SOCKS5 if you need it.
The problem
Common pitfalls
Setting the property too late
Set jdk.http.auth.tunneling.disabledSchemes on the command line or at the very start of main. Changing it after the HTTP client has already authenticated is not reliable.
A SOCKS ProxySelector
HttpClient skips SOCKS proxies without an error and connects directly from your own address. Check the exit IP once after any proxy change.
Answering every authentication request
Check getRequestorType() == RequestorType.PROXY in the authenticator so the proxy password is never offered to a target site that asks for credentials.
A new client for every request
Each client has its own pool and executor. Build one per session or market and reuse it; closing it (Java 21+) releases the threads.
FAQ
Questions
Something not covered? Write to contact@arcproxies.io.
How do I set an authenticated proxy in Java HttpClient?
Use HttpClient.newBuilder().proxy(ProxySelector.of(new InetSocketAddress(host, port))).authenticator(auth), where auth returns a PasswordAuthentication for RequestorType.PROXY. For HTTPS sites, also start the JVM with -Djdk.http.auth.tunneling.disabledSchemes=.
Why does Java HttpClient get 407 even with the right password?
The JDK disables Basic authentication for CONNECT tunnels by default (jdk.http.auth.tunneling.disabledSchemes=Basic in net.properties), so the credentials are never sent for HTTPS targets. Set the property to an empty value.
Does java.net.http.HttpClient support SOCKS5?
No. It ignores SOCKS proxies returned by a ProxySelector and connects directly. Use an HTTP proxy with HttpClient, or another client for SOCKS5.
What does "too many authentication attempts. Limit: 3" mean?
The proxy rejected the credentials the authenticator returned, three times (the default jdk.httpclient.auth.retrylimit). The username or password is wrong; retrying will not help.
Can I use http.proxyHost with HttpClient?
If you build a client without .proxy(...), it uses the default ProxySelector, which reads the http.proxyHost and https.proxyHost system properties. You still need an authenticator for the credentials, so the explicit builder call is clearer.
Sources & review
Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.
- [1]Oracle: Module java.net.http: system properties
- [2]Oracle: HttpClient.Builder
- [3]OpenJDK: conf/net.properties (JDK 21)
- [4]OpenJDK: JDK-8214516: Specify that SOCKS is not supported by java.net.HttpClient
Next
What people build with it
Integrations
Other http clients
Get started
Get credentials and paste them in.
Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.