Java and PHP
Java via HttpClient, OkHttp and Apache HttpClient; PHP via the cURL extension and Guzzle.
Each language has one signature trap: Java's HttpClient will not send credentials to a proxy by default, and PHP's CURLOPT_PROXY needs an explicit proxy type. Both are handled below.
Java 11+ HttpClient
Part of the standard library, but proxy authentication needs an Authenticator and one system property:
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 NextProxyExample {
private static final String GATEWAY_HOST = "GATEWAY_HOST";
private static final int GATEWAY_PORT = 58971;
private static final String PROXY_USER =
"USERNAME-country-US-session-job1-time-30";
private static final String PROXY_PASS = "PASSWORD";
public static void main(String[] args) throws Exception {
// Without this line the JDK refuses to send Basic credentials to the proxy,
// which shows up as a permanent 407
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(
new InetSocketAddress(GATEWAY_HOST, GATEWAY_PORT)))
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
PROXY_USER, PROXY_PASS.toCharArray());
}
})
.connectTimeout(Duration.ofSeconds(15))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ipify.org?format=json"))
.timeout(Duration.ofSeconds(30))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}
OkHttp
Considerably less friction than the standard library; credentials go straight into an Authenticator:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpExample {
static OkHttpClient buildClient(String proxyUser, String proxyPass) {
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("GATEWAY_HOST", 58971));
return new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator((route, response) -> {
// Already sent credentials and still 401/407? Stop, or we loop forever
if (response.request().header("Proxy-Authorization") != null) {
return null;
}
return response.request().newBuilder()
.header("Proxy-Authorization",
Credentials.basic(proxyUser, proxyPass))
.build();
})
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
public static void main(String[] args) throws IOException {
OkHttpClient client = buildClient(
"USERNAME-country-US-session-job1-time-30", "PASSWORD");
Request request = new Request.Builder()
.url("https://api.ipify.org?format=json")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.code() + " " + response.body().string());
}
}
}
SOCKS5 with OkHttp
Proxy proxy = new Proxy(Proxy.Type.SOCKS,
new InetSocketAddress("GATEWAY_HOST", 58971));
// SOCKS5 credentials go through java.net.Authenticator, not proxyAuthenticator
java.net.Authenticator.setDefault(new java.net.Authenticator() {
@Override
protected java.net.PasswordAuthentication getPasswordAuthentication() {
return new java.net.PasswordAuthentication(
"USERNAME-country-US", "PASSWORD".toCharArray());
}
});
OkHttpClient client = new OkHttpClient.Builder().proxy(proxy).build();
Rotating IPs
OkHttp's connection pool reuses connections. To change IP each time, either build a new client or call evictAll():
OkHttpClient client = buildClient("USERNAME-country-US", "PASSWORD");
for (int i = 0; i < 5; i++) {
Request request = new Request.Builder()
.url("https://api.ipify.org")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
// Drain the pool so the next call opens a new connection and re-routes
client.connectionPool().evictAll();
}
Concurrency with per-task sessions
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
public class ConcurrentExample {
public static void main(String[] args) throws Exception {
try (ExecutorService pool = Executors.newFixedThreadPool(8)) {
List<?> futures = IntStream.range(0, 8)
.mapToObj(id -> pool.submit(() -> {
// session values allow only [A-Za-z0-9.-]; dots, not underscores
String user = "USERNAME-country-US-session-task."
+ id + "-time-30";
OkHttpClient client =
OkHttpExample.buildClient(user, "PASSWORD");
Request request = new Request.Builder()
.url("https://api.ipify.org")
.build();
try (Response response =
client.newCall(request).execute()) {
return id + " -> " + response.body().string();
}
}))
.toList();
for (Object future : futures) {
System.out.println(((java.util.concurrent.Future<?>) future).get());
}
}
}
}
Apache HttpClient 5
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.EntityUtils;
public class ApacheExample {
public static void main(String[] args) throws Exception {
HttpHost proxy = new HttpHost("http", "GATEWAY_HOST", 58971);
BasicCredentialsProvider credentials = new BasicCredentialsProvider();
credentials.setCredentials(
new AuthScope(proxy),
new UsernamePasswordCredentials(
"USERNAME-country-US-session-job1-time-30",
"PASSWORD".toCharArray()));
try (CloseableHttpClient client = HttpClients.custom()
.setProxy(proxy)
.setDefaultCredentialsProvider(credentials)
.build()) {
HttpGet request = new HttpGet("https://api.ipify.org?format=json");
client.execute(request, response -> {
System.out.println(response.getCode() + " "
+ EntityUtils.toString(response.getEntity()));
return null;
});
}
}
}
PHP's cURL extension
<?php
declare(strict_types=1);
function proxyRequest(string $url, string $proxyUser, string $proxyPass): string
{
$handle = curl_init($url);
curl_setopt_array($handle, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PROXY => 'GATEWAY_HOST:58971',
// Set the type explicitly; libcurl's default may not be what you expect
CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
CURLOPT_PROXYUSERPWD => $proxyUser . ':' . $proxyPass,
CURLOPT_CONNECTTIMEOUT => 15,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($handle);
if ($body === false) {
$error = curl_error($handle);
curl_close($handle);
throw new RuntimeException('request failed: ' . $error);
}
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status !== 200) {
throw new RuntimeException('target returned HTTP ' . $status);
}
return (string) $body;
}
echo proxyRequest(
'https://api.ipify.org?format=json',
'USERNAME-country-US-session-job1-time-30',
'PASSWORD'
), PHP_EOL;
SOCKS5 in PHP
curl_setopt_array($handle, [
CURLOPT_PROXY => 'GATEWAY_HOST:58971',
// Let the proxy resolve the hostname; equivalent to socks5h://
CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME,
CURLOPT_PROXYUSERPWD => 'USERNAME-country-US:PASSWORD',
]);
Guzzle
composer require guzzlehttp/guzzle
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$proxyUser = 'USERNAME-country-US-session-job1-time-30';
$proxyPass = 'PASSWORD';
$proxy = sprintf('http://%s:%s@GATEWAY_HOST:58971',
rawurlencode($proxyUser), rawurlencode($proxyPass));
$client = new Client([
// Both http and https point at the same plaintext proxy; there is no inbound TLS
'proxy' => ['http' => $proxy, 'https' => $proxy],
'timeout' => 30,
'connect_timeout' => 15,
'http_errors' => false,
]);
try {
$response = $client->get('https://api.ipify.org?format=json');
echo $response->getStatusCode(), ' ', $response->getBody(), PHP_EOL;
} catch (RequestException $exception) {
echo 'request failed: ', $exception->getMessage(), PHP_EOL;
}
Concurrency with Guzzle
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
$password = 'PASSWORD';
$requests = static function () use ($password): Generator {
for ($i = 0; $i < 8; $i++) {
// session values allow only [A-Za-z0-9.-]
$user = sprintf('USERNAME-country-US-session-task.%d-time-30', $i);
$proxy = sprintf('http://%s:%s@GATEWAY_HOST:58971',
rawurlencode($user), rawurlencode($password));
yield new Request('GET', 'https://api.ipify.org') => [
'proxy' => ['http' => $proxy, 'https' => $proxy],
];
}
};
// Guzzle's Pool cannot swap proxies per request, so build a client per session
for ($i = 0; $i < 8; $i++) {
$user = sprintf('USERNAME-country-US-session-task.%d-time-30', $i);
$proxy = sprintf('http://%s:%s@GATEWAY_HOST:58971',
rawurlencode($user), rawurlencode($password));
$client = new Client(['proxy' => ['http' => $proxy, 'https' => $proxy]]);
echo $i, ' -> ', $client->get('https://api.ipify.org')->getBody(), PHP_EOL;
}
Troubleshooting checklist
| Language | Symptom | Cause |
|---|---|---|
| Java | HTTPS always 407, Authenticator never called | jdk.http.auth.tunneling.disabledSchemes="" not set |
| Java | SOCKS5 credentials cross-contaminate | The global Authenticator is process-wide; use HTTP instead |
| Java | IP never changes | OkHttp pool reuse; call evictAll() |
| PHP | Proxy has no effect | CURLOPT_PROXYTYPE not set |
| PHP | Credentials truncated | Password placed in a URL without rawurlencode |
| Any | 400 | invalid_proxy_parameters: option syntax, order or dependency |
| Any | 402 | traffic_exhausted / account_quota_exhausted / quota_exhausted: traffic or quota exhausted |
| Any | 403 | account_disabled / account_expired / access_denied / target_denied: account or policy denial |
| Any | 429 | connection_limit: reduce concurrency and connection rate |
| Any | 502 | Exit connection failed |
| Any | 503 | No eligible route or service temporarily unavailable |
| Any | 504 | Proxy connection establishment timed out |
For HTTP / CONNECT failures, inspect X-NextProxy-Error and JSON error.code. Handle destination statuses and CONNECT handshake exceptions separately. See the error reference for the full mapping.