-
Check your JDK
HttpClient ships with Java 11 and later. Java 11 can also run a single source file directly, which is how the examples below are launched.
Shell java -version -
Allow Basic authentication on proxy tunnels
Since Java 8u111 the JDK disables Basic authentication for HTTPS tunnels by default, so the gateway login fails with a 407. Clear the
jdk.http.auth.tunneling.disabledSchemesproperty on the command line, or in code before the first HttpClient is created.Shell java -Djdk.http.auth.tunneling.disabledSchemes="" ProxyCheck.java -
Send a request through the gateway
Point the client at the gateway with a ProxySelector and answer proxy login requests with an Authenticator. The program prints the exit IP the target sees.
Java // ProxyCheck.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 ProxyCheck { public static void main(String[] args) throws Exception { // Must run before the first HttpClient is built System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); HttpClient client = HttpClient.newBuilder() .proxy(ProxySelector.of(new InetSocketAddress("gw.proxonym.com", 8000))) .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == RequestorType.PROXY) { return new PasswordAuthentication("USERNAME", "PASSWORD".toCharArray()); } return null; } }) .connectTimeout(Duration.ofSeconds(10)) .build(); HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.ipify.org")) .timeout(Duration.ofSeconds(60)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println("Exit IP: " + response.body()); } } -
Rotate, hold or target an IP
Each HttpClient keeps its own connections and login, and the gateway assigns an IP per connection. Use a new client for a new IP, one client per
-session-ID for a sticky IP of up to 120 minutes, and location parameters for targeting.Java // Sessions.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; import java.util.UUID; public class Sessions { static HttpClient clientFor(String username) { return HttpClient.newBuilder() .proxy(ProxySelector.of(new InetSocketAddress("gw.proxonym.com", 8000))) .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, "PASSWORD".toCharArray()) : null; } }) .connectTimeout(Duration.ofSeconds(10)) .build(); } static String exitIp(HttpClient client) throws Exception { HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.ipify.org")) .timeout(Duration.ofSeconds(60)) .build(); return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); } public static void main(String[] args) throws Exception { System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); // Rotating: a new client opens a new connection, so each request gets a new IP for (int i = 0; i < 3; i++) { System.out.println("rotating " + exitIp(clientFor("USERNAME-country-us"))); } // Sticky: one client and one session ID keep the same IP for up to 30 minutes String session = UUID.randomUUID().toString().replace("-", "").substring(0, 8); HttpClient sticky = clientFor("USERNAME-country-us-session-" + session + "-lifetime-30"); for (int i = 0; i < 3; i++) { System.out.println("sticky " + exitIp(sticky)); } // Geo-targeting: London, United Kingdom System.out.println("london " + exitIp(clientFor("USERNAME-country-gb-city-london"))); } } -
Retry gateway errors
A refused tunnel throws an IOException such as
Tunnel failed, got: 502, and a rejected login ends withtoo many authentication attempts. Retry 429, 502 and 504 with a short backoff, and stop on 402, 403 or a failed login, which retries cannot fix.Java static HttpResponse<String> sendWithRetry(HttpClient client, HttpRequest request, int tries) throws IOException, InterruptedException { for (int attempt = 1; ; attempt++) { try { HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); int status = response.statusCode(); if ((status != 429 && status < 500) || attempt == tries) { return response; } } catch (IOException e) { String message = String.valueOf(e.getMessage()); System.err.println("attempt " + attempt + ": " + message); // 402 (balance), 403 (blocked target) and a rejected login will not go away on a retry boolean permanent = message.matches(".*got: 40[23].*") || message.contains("authentication"); if (permanent || attempt == tries) { throw e; } } Thread.sleep(1000L * attempt); } }
Frequently asked questions
Why does Java get a 407 even with the right credentials?
The JDK blocks Basic authentication on HTTPS tunnels by default, so the Authenticator is never used for the CONNECT request. Set jdk.http.auth.tunneling.disabledSchemes to an empty value before the first HttpClient is created, and make sure your Authenticator returns credentials when the requestor type is PROXY.
Can I use OkHttp instead of HttpClient?
Yes. Give the OkHttpClient builder a java.net.Proxy of type HTTP pointing at gw.proxonym.com:8000, and set a proxyAuthenticator that adds a Proxy-Authorization header built with Credentials.basic. OkHttp handles CONNECT authentication itself, so the JDK tunneling property does not apply, and the username parameters work the same way.
How do I get a new IP on every request?
The gateway assigns an IP to each new connection, and an HttpClient keeps its connections open for reuse. Build a fresh client for each request when every call must exit from a different IP, or give each request its own random session ID. Reuse one client when you want a stable, sticky IP.
What does Tunnel failed, got: 502 mean?
HttpClient reports a refused CONNECT request this way, with the gateway status at the end. A 502 means the upstream peer failed and a 504 that the target timed out, and both usually succeed on a retry. A 402 points to an empty balance or expired plan, and a 403 to a target blocked by the acceptable use policy.
