Java · setup guide

Java HttpClient proxy setup

A Java HttpClient proxy takes two builder calls: proxy(ProxySelector.of(...)) for the gateway and authenticator(...) for the credentials. HTTPS targets need one JVM flag as well, because the JDK disables Basic authentication on CONNECT tunnels by default, and without it every HTTPS request comes back 407.
You will need

Residentialresi.proxymonkey.io:8000

ISP or datacenterIP:PORT

CredentialsUSER:PASS

Copy your own from the dashboard, which also lists the host and port for every order. Where it differs from this page, the dashboard is right.

Install

Java 21 or newer, where HttpClient can be closed. The client itself has been in the JDK since 11, and java File.java runs a single source file without a build.

terminal
java -version

Rotating residential

The client keeps connections alive, and a live tunnel keeps its address, so this example builds a new client per request and closes it. Each new client opens a new tunnel through the residential gateway and prints a new address. Run it with java -Djdk.http.auth.tunneling.disabledSchemes= Rotate.java.

Rotate.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 Rotate {
    static final Authenticator auth = new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            if (getRequestorType() != RequestorType.PROXY) return null;
            return new PasswordAuthentication("USER", "PASS".toCharArray());
        }
    };

    public static void main(String[] args) throws Exception {
        URI proxy = URI.create("http://resi.proxymonkey.io:8000");
        ProxySelector selector = ProxySelector.of(new InetSocketAddress(proxy.getHost(), proxy.getPort()));
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://httpbin.org/ip"))
                .timeout(Duration.ofSeconds(30))
                .build();

        for (int i = 0; i < 3; i++) {
            try (HttpClient client = HttpClient.newBuilder()
                    .proxy(selector)
                    .authenticator(auth)
                    .connectTimeout(Duration.ofSeconds(10))
                    .build()) {
                System.out.print(client.send(request, HttpResponse.BodyHandlers.ofString()).body());
            }
        }
    }
}

A static datacenter or ISP IP

On a static datacenter IP, keep one client for the whole run so connections are reused. This is the body of main, with the same imports and Authenticator as above plus java.util.List. For an ISP order, use the host and port from your dashboard.

Static.java
URI proxy = URI.create("http://IP:PORT");
HttpClient client = HttpClient.newBuilder()
        .proxy(ProxySelector.of(new InetSocketAddress(proxy.getHost(), proxy.getPort())))
        .authenticator(auth)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

for (String url : List.of("https://httpbin.org/ip", "https://httpbin.org/headers")) {
    HttpRequest request = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(30)).build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    String body = response.body();
    System.out.println(response.statusCode() + " " + body.substring(0, Math.min(80, body.length())));
}

Keeping one identity

One HttpClient keeps idle connections for 30 seconds by default (jdk.httpclient.keepalive.timeout), and requests to the same host in that window reuse a tunnel and its exit address. That is fine for a short flow and not a guarantee. For a residential IP that has to hold across a login, use a sticky session; the session setting for your account is in the dashboard. Cookies are off unless you add .cookieHandler(new CookieManager()) to the builder.

Specific to Java HttpClient

Things worth knowing

Java proxy authentication over HTTPS needs one flag

Since Java 8u111 the JDK ships jdk.http.auth.tunneling.disabledSchemes=Basic in net.properties, which stops HttpClient and HttpURLConnection alike from answering a CONNECT 407 with Basic credentials. Plain http:// targets work and https:// ones do not. Clear the property on the command line, or set it as the first line of main, before any client exists.

terminal, or the first line of main
java -Djdk.http.auth.tunneling.disabledSchemes= Rotate.java

System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");

Pin HTTP/1.1 when you count bytes

We meter request bytes plus response bytes, headers included. HttpClient prefers HTTP/2, which compresses headers on the wire, so the decoded header map overstates them; pin HTTP/1.1 while you measure. Java does not ask for compressed bodies, so the byte array is what crossed the wire. The request side of a GET is small, and TLS overhead makes our figure slightly higher.

Bytes.java
HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_1_1)
        .proxy(selector)
        .authenticator(auth)
        .build();
HttpResponse<byte[]> r = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
long headers = r.headers().map().entrySet().stream()
        .mapToLong(e -> e.getValue().stream().mapToLong(v -> e.getKey().length() + v.length() + 4).sum())
        .sum();
System.out.println("received ~" + (headers + r.body().length) + " bytes");

Several proxies in one JVM

Authenticator.setDefault is global to the JVM. A per-client authenticator(...), as on this page, lets a residential client and a datacenter client run side by side with different credentials, and a custom ProxySelector can choose a proxy per URI.

When it breaks

Common errors and fixes

  • HTTPS requests return 407 even though an Authenticator is set

    Why
    Basic authentication is disabled for CONNECT tunnels by default, so the client never sends the credentials. Plain http:// URLs through the same proxy work, which is the giveaway.
    Fix
    Start the JVM with -Djdk.http.auth.tunneling.disabledSchemes= (an empty value), then check the credentials if it persists.
  • IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 ..."

    Why
    The same default, hit through HttpURLConnection in older code. It uses the global Authenticator.setDefault unless you call setAuthenticator on the connection.
    Fix
    Clear jdk.http.auth.tunneling.disabledSchemes as above and register an authenticator that answers RequestorType.PROXY.
  • java.net.http.HttpTimeoutException: request timed out

    Why
    The response took longer than the request's timeout(...). Without one, send can wait indefinitely.
    Fix
    Set connectTimeout on the builder and timeout on each request, catch the exception and retry. With a new client on the rotating gateway, the retry leaves from a new address.
  • javax.net.ssl.SSLHandshakeException: PKIX path building failed

    Why
    The JVM's trust store lacks the target's CA, often a corporate root. The proxy takes no part in TLS on a tunnel.
    Fix
    Import the CA with keytool and point -Djavax.net.ssl.trustStore at that store. A trust-all SSLContext is for testing only.

A failed connection that moved no data is not billed. Retries you send are billed like any other request, and each shows as its own line in your usage log.

The community layer

Java HttpClient still misbehaving?

Paste the error and the few lines that set up the proxy into Discord, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk