15% off your first purchase with code WELCOME15, up to 25 GB.

See pricing

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

your proxy, as a URL
http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000

The short answer

  • Start the JVM with -Djdk.http.auth.tunneling.disabledSchemes=; the JDK default is Basic, which blocks Basic proxy credentials on CONNECT tunnels.
  • ProxySelector.of(...) plus an Authenticator that answers RequestorType.PROXY.
  • One client keeps its tunnels: use one HttpClient per 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

  1. 01

    Allow Basic authentication on CONNECT tunnels

    The JDK's net.properties ships with jdk.http.auth.tunneling.disabledSchemes=Basic. Override it on the command line, or with System.setProperty as the first line of main, before any client sends a request. Without it, HTTPS requests return status 407 with no exception.

    shell
    java -Djdk.http.auth.tunneling.disabledSchemes= -jar crawler.jar# or, first line of main():#   System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
  2. 02

    Build a client and check the exit IP

    The authenticator answers the gateway's 407 challenge with the ARC username. connectTimeout bounds new connections; the request timeout bounds the wait for the response headers.

    ExitIp.java
    import 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());    }}
  3. 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, HttpClient is AutoCloseable.

    inside main()
    // 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());    }}
  4. 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()
    Map<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());    }}
  5. 05

    Handle 407, timeouts and retries

    With a wrong password the authenticator is asked again until the retry limit (3 by default), then send throws an IOException saying "too many authentication attempts". Timeouts raise HttpTimeoutException (connect: HttpConnectTimeoutException). HttpClient does not retry 429 or 5xx for you.

    a static method next to main()
    static 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-Authorization header 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. [1]Oracle: Module java.net.http: system properties
  2. [2]Oracle: HttpClient.Builder
  3. [3]OpenJDK: conf/net.properties (JDK 21)
  4. [4]OpenJDK: JDK-8214516: Specify that SOCKS is not supported by java.net.HttpClient

Next

What people build with it

Use caseProxies for web scraping and data collectionA second provider that just works — Scrapy, requests, Crawlee or Playwright, self-serve, no sales call, bandwidth that never expires.Use caseProxies for alternative data in financeTurn public listings, pricing and hiring signals into a time series your models can trade on — collected on schedule, from the market you are modelling.Use caseProxies for market research and sentimentSample what a market actually publishes — catalogues, forums, reviews and news — from inside that market, on a schedule you can repeat and defend.GlossaryHTTP CONNECTHTTP CONNECT is the HTTP request method a client sends to a proxy to ask for a tunnel to a host and port; once the proxy answers with a 2xx status it blindly forwards bytes in both directions, which is how HTTPS passes through an HTTP proxy with TLS intact end to end (RFC 9110, section 9.3.6).GlossarySOCKS5SOCKS5 is a proxy protocol, standardised in RFC 1928 in 1996, that relays TCP connections and UDP datagrams between a client and a destination without interpreting the application traffic, with username/password authentication defined separately in RFC 1929.GuideRotating vs sticky sessionsRotating proxies change exit IP per connection; sticky sessions hold one IP for a set time. How each works, when to use which, and how to set them up.GuideHow to test a proxy's locationCheck a proxy's exit IP and geolocation with curl, why IP geolocation databases disagree, and how to verify what the target site actually sees.IntegrationC# HttpClientUse an authenticated proxy in .NET: WebProxy and NetworkCredential on SocketsHttpHandler, a client per sticky session, SOCKS5 and 407 handling.

Integrations

Other http clients

Python requestsUse an authenticated proxy with Python requests: the proxies argument, sticky sessions per task, geo-targeting, SOCKS5 and retries that skip 407 errors.HTTPXConfigure an authenticated proxy in HTTPX 0.28: the proxy= argument that replaced proxies=, mounts per domain, async clients, SOCKS5 and error handling.aiohttpUse an authenticated proxy with aiohttp: proxy= per request or per session, why proxy_auth is deprecated, SOCKS5 through aiohttp-socks, and 407 handling.AxiosUse an authenticated proxy with Axios in Node.js: the proxy option on 1.16.1+, https-proxy-agent for older versions, sticky sessions, SOCKS5 and 407s.Node.js fetchNode's built-in fetch has no proxy option. Use undici's ProxyAgent as the dispatcher, or NODE_USE_ENV_PROXY, with sticky sessions, SOCKS5 and 407 handling.curlSend curl through an authenticated proxy with -x and -U, check the exit IP, test sticky sessions and countries, use socks5h, and read 407 failures.GoRoute Go's net/http through an authenticated proxy: Transport.Proxy with url.UserPassword, a sticky session per request context, SOCKS5 and 407 checks.RubyUse an authenticated proxy in Ruby: Net::HTTP proxy arguments, Faraday proxy options, sticky sessions per task, geo-targeting and 407 handling.PHP GuzzleRoute PHP Guzzle and plain cURL through an authenticated proxy: the proxy option, sticky sessions, geo-targeting, retries that stop on a 407.C# HttpClientUse an authenticated proxy in .NET: WebProxy and NetworkCredential on SocketsHttpHandler, a client per sticky session, SOCKS5 and 407 handling.Rust reqwestRoute Rust reqwest through an authenticated proxy: Proxy::all with basic_auth, a Client per sticky session, socks5h, timeouts and 407 errors.wgetRun GNU Wget through an authenticated proxy: https_proxy, --proxy-user, a .wgetrc for credentials, sticky sessions for mirrors and exit codes.PostmanSend Postman requests through an authenticated proxy: the custom proxy settings, Proxy auth, checking the exit IP, sessions, countries and Newman.DockerGive Docker containers and builds an authenticated proxy: HTTP_PROXY, HTTPS_PROXY and NO_PROXY, build args, Compose, and why the daemon proxy differs.

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.