Pourquoi utiliser un proxy HTTP en Java
Les développeurs Java ont souvent besoin de proxies HTTP pour des cas d'usage critiques : scraping web à grande échelle, tests d'API géo-restreintes, monitoring de prix concurrentiel, ou contournement des limitations de taux. Sans proxy approprié, vos requêtes échouent, se font bloquer, ou révèlent votre infrastructure.
Ce guide couvre les quatre principales approches pour utiliser des proxies HTTP en Java moderne :
- Java 11+ HttpClient — l'API standard avec ProxySelector et Authenticator
- OkHttp — le client HTTP le plus populaire de l'écosystème JVM
- Jsoup — parsing HTML avec support proxy intégré
- Apache HttpClient — pour les systèmes legacy encore répandus
Nous aborderons également le parallélisme avec ExecutorService, la gestion TLS, et les patterns de production (retry, timeouts, connection pooling).
Java 11+ HttpClient avec ProxySelector
L'API java.net.http.HttpClient introduite dans Java 11 offre une approche moderne et réactive. La configuration proxy passe par ProxySelector et l'authentification par Authenticator.
Configuration basique avec proxy HTTP
Pour un proxy sans authentification, utilisez ProxySelector.of() avec un InetSocketAddress :
import java.net.InetSocketAddress;
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 BasicProxyExample {
public static void main(String[] args) throws Exception {
// Proxy HTTP sur gate.proxyhat.com:8080
var proxyAddress = new InetSocketAddress("gate.proxyhat.com", 8080);
var proxySelector = ProxySelector.of(proxyAddress);
var client = HttpClient.newBuilder()
.proxy(proxySelector)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/ip"))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}
Proxy authentifié avec Authenticator
Les proxies résidentiels et mobiles nécessitent une authentification. Authenticator fournit les credentials à la demande :
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 AuthenticatedProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String USERNAME = "user-country-FR"; // Geo-targeting France
private static final String PASSWORD = "your_password";
public static void main(String[] args) throws Exception {
var proxyAddress = new InetSocketAddress(PROXY_HOST, PROXY_PORT);
var proxySelector = ProxySelector.of(proxyAddress);
// Authenticator pour l'authentification proxy
var authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// Vérifier que c'est bien une demande du proxy
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
}
return null;
}
};
var client = HttpClient.newBuilder()
.proxy(proxySelector)
.authenticator(authenticator)
.connectTimeout(Duration.ofSeconds(15))
.build();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/ip"))
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("IP vue par le serveur: " + response.body());
}
}
Proxy SOCKS5 avec HttpClient
Pour SOCKS5, utilisez le port 1080 et créez un ProxySelector personnalisé :
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.util.List;
import java.net.http.HttpClient;
public class Socks5ProxySelector extends ProxySelector {
private final InetSocketAddress proxyAddress;
public Socks5ProxySelector(String host, int port) {
this.proxyAddress = new InetSocketAddress(host, port);
}
@Override
public List<Proxy> select(URI uri) {
return List.of(new Proxy(Proxy.Type.SOCKS, proxyAddress));
}
@Override
public void connectFailed(URI uri, java.net.SocketAddress sa, IOException e) {
System.err.println("Connexion SOCKS échouée: " + uri + " - " + e.getMessage());
}
}
// Usage
var socksSelector = new Socks5ProxySelector("gate.proxyhat.com", 1080);
var client = HttpClient.newBuilder()
.proxy(socksSelector)
.authenticator(authenticator)
.build();
OkHttp avec proxy et authentification
OkHttp reste le client HTTP le plus utilisé sur la JVM. Sa gestion des proxies est explicite via Proxy et Authenticator.
Configuration OkHttp avec proxy authentifié
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class OkHttpProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
public static void main(String[] args) throws Exception {
// Proxy HTTP
var proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
// Authenticator pour le proxy
Authenticator proxyAuthenticator = new Authenticator() {
@Override
public Request authenticate(Route route, Response response) {
if (response.request().header("Proxy-Authorization") != null) {
return null; // Échec d'authentification, abandonner
}
String credential = Credentials.basic("user-country-US", "your_password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
};
var client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
var request = new Request.Builder()
.url("https://httpbin.org/headers")
.header("User-Agent", "Mozilla/5.0")
.build();
try (var response = client.newCall(request).execute()) {
System.out.println("Status: " + response.code());
System.out.println("Body: " + response.body().string());
}
}
}
Connection pooling et retry avec OkHttp
OkHttp gère automatiquement le connection pooling. Configurez-le pour des workloads intensifs :
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
public class OptimizedOkHttpClient {
public static OkHttpClient createPooledClient() {
var connectionPool = new ConnectionPool(
50, // max idle connections
5, // keep-alive duration
TimeUnit.MINUTES
);
return new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator((route, response) -> {
String credential = Credentials.basic("user-country-DE", "password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.connectionPool(connectionPool)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
// Interceptor pour logging en développement
.addInterceptor(chain -> {
long start = System.nanoTime();
var request = chain.request();
System.out.printf("[%s] %s%n", request.method(), request.url());
return chain.proceed(request);
})
.build();
}
}
Jsoup avec support proxy pour parsing HTML
Jsoup simplifie l'extraction de données HTML. Il supporte les proxies via Proxy ou la configuration système.
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.net.InetSocketAddress;
import java.net.Proxy;
public class JsoupProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
public static void main(String[] args) throws Exception {
// Créer le proxy HTTP
var proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT));
// Jsoup avec authentification proxy via headers
String auth = java.util.Base64.getEncoder()
.encodeToString(("user-country-FR:your_password").getBytes());
Document doc = Jsoup.connect("https://example.com/products")
.proxy(proxy)
.header("Proxy-Authorization", "Basic " + auth)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(30000)
.followRedirects(true)
.get();
// Extraction des données
Elements products = doc.select(".product-item");
for (Element product : products) {
String name = product.select("h3.product-name").text();
String price = product.select(".price").text();
String link = product.select("a").attr("abs:href");
System.out.printf("Produit: %s | Prix: %s | URL: %s%n", name, price, link);
}
}
}
Pattern réutilisable pour scraping avec Jsoup
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.net.Proxy;
import java.util.Optional;
public class JsoupScraper {
private final Proxy proxy;
private final String proxyAuth;
public JsoupScraper(String proxyHost, int proxyPort,
String username, String password) {
this.proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort));
this.proxyAuth = java.util.Base64.getEncoder()
.encodeToString((username + ":" + password).getBytes());
}
public Optional<Document> fetchDocument(String url, int timeoutMs) {
try {
return Optional.of(Jsoup.connect(url)
.proxy(proxy)
.header("Proxy-Authorization", "Basic " + proxyAuth)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
.timeout(timeoutMs)
.maxBodySize(10 * 1024 * 1024) // 10 MB max
.get());
} catch (Exception e) {
System.err.println("Erreur fetch " + url + ": " + e.getMessage());
return Optional.empty();
}
}
public Optional<Document> fetchWithRetry(String url, int maxRetries) {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
var doc = fetchDocument(url, 30000);
if (doc.isPresent()) return doc;
try {
Thread.sleep(1000 * attempt); // Backoff exponentiel
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return Optional.empty();
}
}
return Optional.empty();
}
}
Apache HttpClient (legacy mais toujours pertinent)
De nombreux systèmes d'entreprise utilisent encore Apache HttpClient. Voici la configuration proxy :
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpGet;
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.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.routing.HttpRoute;
import org.apache.hc.core5.http.HttpHost;
import java.net.URI;
public class ApacheHttpClientProxyExample {
public static void main(String[] args) throws Exception {
// Connection pooling
var connManager = new PoolingHttpClientConnectionManager();
connManager.setMaxTotal(100);
connManager.setDefaultMaxPerRoute(20);
// Configuration proxy
var proxyHost = new HttpHost("http", "gate.proxyhat.com", 8080);
// Credentials pour le proxy
var credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope(proxyHost),
new UsernamePasswordCredentials("user-country-GB", "password".toCharArray())
);
try (CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(connManager)
.setProxy(proxyHost)
.setDefaultCredentialsProvider(credentialsProvider)
.build()) {
var request = new HttpGet("https://httpbin.org/ip");
var response = client.execute(request);
String body = new String(response.getEntity().getContent().readAllBytes());
System.out.println("Response: " + body);
}
}
}
Scraping parallèle avec ExecutorService et pool de proxies
Pour des workloads intensifs, combinez ExecutorService avec un pool de proxies résidentiels rotatifs :
import java.net.InetSocketAddress;
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.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ParallelScraper {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String USERNAME_TEMPLATE = "user-country-%s-session-%s";
private static final String PASSWORD = "your_password";
private final HttpClient baseClient;
private final ExecutorService executor;
private final AtomicInteger sessionCounter = new AtomicInteger(0);
public ParallelScraper(int threadCount) {
this.executor = Executors.newFixedThreadPool(threadCount);
// Client de base - le proxy sera configuré par requête
this.baseClient = HttpClient.newBuilder()
.executor(executor)
.connectTimeout(Duration.ofSeconds(15))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
}
private HttpClient createClientWithSession(String country) {
String username = String.format(USERNAME_TEMPLATE, country,
sessionCounter.getAndIncrement());
var proxySelector = ProxySelector.of(
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
var authenticator = new java.net.Authenticator() {
@Override
protected java.net.PasswordAuthentication getPasswordAuthentication() {
return new java.net.PasswordAuthentication(username, PASSWORD.toCharArray());
}
};
return HttpClient.newBuilder()
.proxy(proxySelector)
.authenticator(authenticator)
.connectTimeout(Duration.ofSeconds(15))
.build();
}
public CompletableFuture<List<String>> scrapeUrls(List<String> urls, String country) {
List<CompletableFuture<String>> futures = urls.stream()
.map(url -> scrapeWithRetry(url, country, 3))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.toList());
}
private CompletableFuture<String> scrapeWithRetry(String url, String country, int maxRetries) {
return scrapeWithRetryInternal(url, country, maxRetries, 0);
}
private CompletableFuture<String> scrapeWithRetryInternal(
String url, String country, int maxRetries, int attempt) {
var client = createClientWithSession(country);
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.handle((response, ex) -> {
if (ex != null || response.statusCode() >= 500) {
if (attempt < maxRetries) {
System.err.printf("Retry %d/%d for %s%n", attempt + 1, maxRetries, url);
try {
Thread.sleep(1000L * (attempt + 1)); // Backoff
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return scrapeWithRetryInternal(url, country, maxRetries, attempt + 1);
}
return CompletableFuture.completedFuture("ERROR: " + url);
}
return CompletableFuture.completedFuture(
response.statusCode() + ":" + response.body().substring(0, Math.min(100, response.body().length()))
);
})
.thenCompose(cf -> cf);
}
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
public static void main(String[] args) throws Exception {
var scraper = new ParallelScraper(10);
var urls = List.of(
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
"https://httpbin.org/get"
);
try {
var results = scraper.scrapeUrls(urls, "US").join();
results.forEach(System.out::println);
} finally {
scraper.shutdown();
}
}
}
Considérations TLS et SSLContext personnalisé
Certains proxies effectent du TLS interception (man-in-the-middle). D'autres serveurs utilisent des certificats auto-signés. Java JSSE permet de personnaliser la validation :
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.net.http.HttpClient;
import java.security.cert.X509Certificate;
public class TlsConfig {
// ATTENTION: Ne jamais utiliser en production sans validation
// Crée un SSLContext qui ignore la validation des certificats
// Utile pour les tests ou les environnements avec proxies interceptants
public static SSLContext createTrustAllContext() throws Exception {
var trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
return sslContext;
}
// Version sécurisée avec TrustStore personnalisé
public static SSLContext createCustomTrustStore(String trustStorePath,
String trustStorePassword) throws Exception {
var trustStore = java.security.KeyStore.getInstance("JKS");
try (var is = java.nio.file.Files.newInputStream(java.nio.file.Path.of(trustStorePath))) {
trustStore.load(is, trustStorePassword.toCharArray());
}
var trustManagerFactory = javax.net.ssl.TrustManagerFactory.getInstance(
javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm()
);
trustManagerFactory.init(trustStore);
var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext;
}
public static HttpClient createClientWithCustomSsl(SSLContext sslContext) {
return HttpClient.newBuilder()
.sslContext(sslContext)
.sslParameters(new javax.net.ssl.SSLParameters())
.connectTimeout(java.time.Duration.ofSeconds(15))
.build();
}
}
Configuration TLS avec OkHttp
import okhttp3.OkHttpClient;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
public class OkHttpTlsConfig {
public static OkHttpClient createTrustAllClient() throws Exception {
var trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new javax.net.ssl.TrustManager[]{trustManager}, null);
return new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.hostnameVerifier((hostname, session) -> true)
.build();
}
}
Bonnes pratiques et patterns de production
Gestion des timeouts
Configurez systématiquement des timeouts pour éviter les threads bloqués :
| Timeout | Valeur recommandée | Description |
|---|---|---|
| Connection | 10-15 secondes | Établissement de la connexion TCP |
| Read | 30-60 secondes | Lecture des données |
| Write | 30 secondes | Envoi des données |
| Request | 60-120 secondes | Timeout global de la requête |
Retry avec circuit breaker
Implémentez un pattern retry avec exponential backoff et circuit breaker pour les APIs instables :
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class RetryPolicy {
private final int maxAttempts;
private final Duration initialDelay;
private final Duration maxDelay;
private final double backoffMultiplier;
public RetryPolicy(int maxAttempts, Duration initialDelay,
Duration maxDelay, double backoffMultiplier) {
this.maxAttempts = maxAttempts;
this.initialDelay = initialDelay;
this.maxDelay = maxDelay;
this.backoffMultiplier = backoffMultiplier;
}
public <T> T execute(Supplier<T> action) {
Exception lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return action.get();
} catch (Exception e) {
lastException = e;
if (attempt == maxAttempts) break;
long delayMs = (long) Math.min(
initialDelay.toMillis() * Math.pow(backoffMultiplier, attempt - 1),
maxDelay.toMillis()
);
System.err.printf("Tentative %d échouée, retry dans %dms: %s%n",
attempt, delayMs, e.getMessage());
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted pendant retry", ie);
}
}
}
throw new RuntimeException("Échec après " + maxAttempts + " tentatives", lastException);
}
// Usage
public static void main(String[] args) {
var policy = new RetryPolicy(3, Duration.ofSeconds(1), Duration.ofSeconds(30), 2.0);
String result = policy.execute(() -> {
// Appel HTTP ici
return "success";
});
System.out.println(result);
}
}
Points clés à retenir
- Java 11+ HttpClient offre une API moderne avec
ProxySelectoretAuthenticator— idéal pour les nouveaux projets.- OkHttp reste le choix le plus robuste pour la production avec connection pooling automatique et retry intégré.
- Jsoup permet le parsing HTML direct avec proxy — parfait pour le scraping simple.
- Toujours configurer des timeouts explicites pour éviter les blocages.
- Utiliser des sessions sticky (
user-session-xxx) pour les requêtes nécessitant la même IP.- Implémenter un retry avec exponential backoff pour la résilience.
- Attention au TLS interception par certains proxies — configurez
SSLContextsi nécessaire.
Conclusion
L'utilisation de proxies HTTP en Java nécessite de choisir le bon client selon vos besoins : HttpClient pour les projets Java modernes, OkHttp pour la robustesse production, Jsoup pour le scraping HTML simple. Configurez systématiquement l'authentification, les timeouts, et implémentez des stratégies de retry pour des applications fiables.
Pour des workloads intensifs, le parallélisme avec ExecutorService combiné à un pool de proxies résidentiels rotatifs offre les meilleures performances. Consultez notre page de tarification pour les options de proxies résidentiels et mobiles adaptées à votre cas d'usage.






