Guia Completo de Proxy HTTP em Java: HttpClient, OkHttp, Jsoup e Apache

Aprenda a configurar proxies HTTP em Java 17+ com exemplos práticos usando HttpClient, OkHttp, Jsoup e Apache HttpClient. Inclui autenticação, pool de conexões, scraping paralelo e configuração TLS.

Guia Completo de Proxy HTTP em Java: HttpClient, OkHttp, Jsoup e Apache

Configurar proxies HTTP em Java não é tão direto quanto em linguagens como Python ou Node.js. A plataforma oferece múltiplas APIs de cliente HTTP — java.net.http.HttpClient (desde o Java 11), OkHttp, Apache HttpClient, e bibliotecas de scraping como Jsoup — cada uma com sua própria abordagem para proxy. Este guia mostra como configurar proxies autenticados, lidar com rotação de IPs, e construir aplicações de scraping robustas em Java 17+.

Por que usar proxies HTTP em aplicações Java?

Aplicações Java que fazem requisições HTTP frequentemente precisam de proxies por três motivos principais:

  • Rate limiting: APIs e sites limitam requisições por IP. Proxies residenciais permitem distribuir requisições.
  • Geo-targeting: Acessar conteúdo restrito por região usando IPs de países específicos.
  • Anonimato: Evitar bloqueios em scraping de dados públicos.

O Java 17+ oferece APIs modernas, mas a integração com proxies autenticados requer configuração específica. Vamos cobrir cada cliente HTTP popular.

Java 11+ HttpClient com ProxySelector e Authenticator

O java.net.http.HttpClient introduzido no Java 11 é o cliente HTTP moderno padrão. Ele suporta HTTP/2, WebSocket, e APIs reativas. Para usar proxies, você precisa configurar um ProxySelector e, para autenticação, um Authenticator.

Configuração básica com ProxySelector

import java.net.InetSocketAddress;
import java.net.Proxy;
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.util.List;

public class HttpClientProxyExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    
    public static void main(String[] args) throws Exception {
        // Criar ProxySelector que direciona todas as requisições pelo proxy
        ProxySelector proxySelector = new ProxySelector() {
            @Override
            public List<Proxy> select(URI uri) {
                return List.of(new Proxy(Proxy.Type.HTTP, 
                    new InetSocketAddress(PROXY_HOST, PROXY_PORT)));
            }
            
            @Override
            public void connectFailed(URI uri, SocketAddress sa, IOException e) {
                System.err.println("Falha na conexão proxy: " + e.getMessage());
            }
        };
        
        HttpClient client = HttpClient.newBuilder()
            .proxy(proxySelector)
            .connectTimeout(Duration.ofSeconds(30))
            .build();
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://httpbin.org/ip"))
            .GET()
            .build();
        
        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        
        System.out.println("Status: " + response.statusCode());
        System.out.println("Body: " + response.body());
    }
}

Autenticação de proxy com Authenticator

Proxies comerciais como ProxyHat exigem autenticação. O HttpClient usa Authenticator para fornecer credenciais:

import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.http.HttpClient;

public class HttpClientAuthenticatedProxy {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String USERNAME = "user-country-BR";  // Geo-targeting Brasil
    private static final String PASSWORD = "sua_senha";
    
    public static HttpClient createClient() {
        Authenticator auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                if (getRequestorType() == RequestorType.PROXY) {
                    return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
                }
                return null;
            }
        };
        
        ProxySelector proxySelector = ProxySelector.of(
            new InetSocketAddress(PROXY_HOST, PROXY_PORT));
        
        return HttpClient.newBuilder()
            .proxy(proxySelector)
            .authenticator(auth)
            .connectTimeout(Duration.ofSeconds(30))
            .followRedirects(HttpClient.Redirect.NORMAL)
            .build();
    }
    
    public static void main(String[] args) throws Exception {
        HttpClient client = createClient();
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://httpbin.org/ip"))
            .timeout(Duration.ofSeconds(60))
            .GET()
            .build();
        
        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        
        System.out.println(response.body());
    }
}
Nota: O Authenticator verifica getRequestorType() para diferenciar entre autenticação de proxy (PROXY) e autenticação do servidor destino (SERVER). Isso é essencial quando ambos exigem credenciais.

Requisições assíncronas com CompletableFuture

import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.stream.Collectors;

public class AsyncProxyRequests {
    
    private final HttpClient client;
    
    public AsyncProxyRequests() {
        this.client = HttpClientAuthenticatedProxy.createClient();
    }
    
    public CompletableFuture<HttpResponse<String>> fetchAsync(String url) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .GET()
            .build();
        
        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
    }
    
    public void fetchMultiple(List<String> urls) {
        List<CompletableFuture<HttpResponse<String>>> futures = urls.stream()
            .map(this::fetchAsync)
            .collect(Collectors.toList());
        
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenAccept(v -> {
                futures.forEach(f -> {
                    try {
                        HttpResponse<String> resp = f.get();
                        System.out.println(resp.uri() + " -> " + resp.statusCode());
                    } catch (Exception e) {
                        System.err.println("Erro: " + e.getMessage());
                    }
                });
            })
            .join();
    }
    
    public static void main(String[] args) {
        AsyncProxyRequests scraper = new AsyncProxyRequests();
        List<String> urls = List.of(
            "https://httpbin.org/ip",
            "https://httpbin.org/headers",
            "https://httpbin.org/user-agent"
        );
        scraper.fetchMultiple(urls);
    }
}

OkHttp com Proxy e Authenticator

OkHttp da Square é amplamente usado no ecossistema Android e backend Java. Sua API é mais intuitiva para configuração de proxies comparada ao HttpClient nativo.

Configuração básica com proxy autenticado

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.util.concurrent.TimeUnit;

public class OkHttpProxyExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String USERNAME = "user-country-US";
    private static final String PASSWORD = "sua_senha";
    
    public static OkHttpClient createClient() {
        Proxy proxy = new Proxy(Proxy.Type.HTTP, 
            new InetSocketAddress(PROXY_HOST, PROXY_PORT));
        
        Authenticator proxyAuthenticator = new Authenticator() {
            @Override
            public Request authenticate(Route route, Response response) {
                // Evitar loop infinito de autenticação
                if (response.responseCount() >= 3) {
                    return null;
                }
                String credential = Credentials.basic(USERNAME, PASSWORD);
                return response.request().newBuilder()
                    .header("Proxy-Authorization", credential)
                    .build();
            }
        };
        
        return new OkHttpClient.Builder()
            .proxy(proxy)
            .proxyAuthenticator(proxyAuthenticator)
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build();
    }
    
    public static void main(String[] args) throws Exception {
        OkHttpClient client = createClient();
        
        Request request = new Request.Builder()
            .url("https://httpbin.org/ip")
            .build();
        
        try (Response response = client.newCall(request).execute()) {
            System.out.println("Status: " + response.code());
            System.out.println("Body: " + response.body().string());
        }
    }
}

Pool de conexões e configuração avançada

OkHttp gerencia automaticamente um pool de conexões. Para aplicações de alto volume, configure o ConnectionPool explicitamente:

import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;

public class OkHttpConnectionPool {
    
    public static OkHttpClient createOptimizedClient() {
        // Pool com 50 conexões idle, timeout de 5 minutos
        ConnectionPool connectionPool = new ConnectionPool(50, 5, TimeUnit.MINUTES);
        
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
            new InetSocketAddress("gate.proxyhat.com", 8080));
        
        return new OkHttpClient.Builder()
            .proxy(proxy)
            .proxyAuthenticator((route, response) -> {
                String credential = Credentials.basic("user-country-DE", "sua_senha");
                return response.request().newBuilder()
                    .header("Proxy-Authorization", credential)
                    .build();
            })
            .connectionPool(connectionPool)
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            // Interceptor para logging
            .addInterceptor(chain -> {
                long start = System.nanoTime();
                Request request = chain.request();
                Response response = chain.proceed(request);
                long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
                System.out.printf("[%dms] %s -> %d%n", 
                    elapsed, request.url(), response.code());
                return response;
            })
            .build();
    }
}

Jsoup com suporte a proxy para parsing HTML

Jsoup é a biblioteca padrão para parsing HTML em Java. Ela pode buscar URLs diretamente, com suporte a proxies via Connection.

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.net.Proxy;
import java.net.InetSocketAddress;

public class JsoupProxyExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String USERNAME = "user-country-BR";
    private static final String PASSWORD = "sua_senha";
    
    public static Document fetchWithProxy(String url) throws Exception {
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
            new InetSocketAddress(PROXY_HOST, PROXY_PORT));
        
        return Jsoup.connect(url)
            .proxy(proxy)
            .header("Proxy-Authorization", 
                java.util.Base64.getEncoder().encodeToString(
                    (USERNAME + ":" + PASSWORD).getBytes()))
            .timeout(30000)
            .followRedirects(true)
            .ignoreHttpErrors(true)
            .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
                "AppleWebKit/537.36 (KHTML, like Gecko) " +
                "Chrome/120.0.0.0 Safari/537.36")
            .get();
    }
    
    public static void main(String[] args) throws Exception {
        Document doc = fetchWithProxy("https://example.com");
        
        // Extrair links
        Elements links = doc.select("a[href]");
        for (Element link : links) {
            System.out.println(link.attr("abs:href") + " - " + link.text());
        }
        
        // Extrair metadados
        String title = doc.title();
        String description = doc.select("meta[name=description]").attr("content");
        
        System.out.println("Title: " + title);
        System.out.println("Description: " + description);
    }
}

Integração Jsoup + OkHttp para maior controle

Para aplicações de produção, combine OkHttp (para requisições robustas) com Jsoup (para parsing):

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class JsoupOkHttpIntegration {
    
    private final OkHttpClient httpClient;
    
    public JsoupOkHttpIntegration() {
        this.httpClient = OkHttpProxyExample.createClient();
    }
    
    public Document fetchAndParse(String url) throws Exception {
        Request request = new Request.Builder()
            .url(url)
            .header("Accept", "text/html,application/xhtml+xml")
            .header("Accept-Language", "pt-BR,pt;q=0.9,en;q=0.8")
            .build();
        
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new RuntimeException("Falha: " + response.code());
            }
            
            String html = response.body().string();
            String baseUrl = response.request().url().toString();
            
            // Parse com Jsoup, preservando base URL para links absolutos
            return Jsoup.parse(html, baseUrl);
        }
    }
    
    public static void main(String[] args) throws Exception {
        JsoupOkHttpIntegration scraper = new JsoupOkHttpIntegration();
        Document doc = scraper.fetchAndParse("https://news.ycombinator.com");
        
        doc.select(".titleline > a").forEach(link -> {
            System.out.println(link.text() + " -> " + link.attr("href"));
        });
    }
}

Apache HttpClient (para ecossistemas legados)

Muitas empresas ainda usam Apache HttpClient 4.x em sistemas legados. A configuração de proxy é mais verbosa:

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class ApacheHttpClientProxy {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String USERNAME = "user-country-US";
    private static final String PASSWORD = "sua_senha";
    
    public static CloseableHttpClient createClient() {
        HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT);
        
        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(
            new AuthScope(PROXY_HOST, PROXY_PORT),
            new UsernamePasswordCredentials(USERNAME, PASSWORD)
        );
        
        RequestConfig config = RequestConfig.custom()
            .setProxy(proxy)
            .setConnectTimeout(30000)
            .setSocketTimeout(60000)
            .build();
        
        return HttpClients.custom()
            .setDefaultCredentialsProvider(credsProvider)
            .setDefaultRequestConfig(config)
            .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy())
            .build();
    }
    
    public static void main(String[] args) throws Exception {
        try (CloseableHttpClient client = createClient()) {
            HttpGet request = new HttpGet("https://httpbin.org/ip");
            
            try (CloseableHttpResponse response = client.execute(request)) {
                String body = EntityUtils.toString(response.getEntity());
                System.out.println(body);
            }
        }
    }
}

Scraping paralelo com pool de proxies residenciais

Para scraping de alta escala, use um ExecutorService com rotação de proxies. Este exemplo demonstra como distribuir requisições entre múltiplos IPs:

import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class ParallelProxyScraper {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String BASE_USER = "user-session-";
    private static final String PASSWORD = "sua_senha";
    
    private final ExecutorService executor;
    private final AtomicInteger sessionCounter = new AtomicInteger(0);
    
    public ParallelProxyScraper(int threadCount) {
        this.executor = Executors.newFixedThreadPool(threadCount);
    }
    
    private HttpClient createClientWithSession() {
        // Cada thread usa uma sessão diferente = IP diferente
        String sessionUser = BASE_USER + sessionCounter.getAndIncrement();
        
        Authenticator auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                if (getRequestorType() == RequestorType.PROXY) {
                    return new PasswordAuthentication(sessionUser, PASSWORD.toCharArray());
                }
                return null;
            }
        };
        
        ProxySelector proxySelector = ProxySelector.of(
            new InetSocketAddress(PROXY_HOST, PROXY_PORT));
        
        return HttpClient.newBuilder()
            .proxy(proxySelector)
            .authenticator(auth)
            .connectTimeout(Duration.ofSeconds(30))
            .build();
    }
    
    public List<String> scrapeUrls(List<String> urls) {
        List<CompletableFuture<String>> futures = new ArrayList<>();
        
        for (String url : urls) {
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                try {
                    HttpClient client = createClientWithSession();
                    HttpRequest request = HttpRequest.newBuilder()
                        .uri(URI.create(url))
                        .timeout(Duration.ofSeconds(60))
                        .GET()
                        .build();
                    
                    HttpResponse<String> response = client.send(request,
                        HttpResponse.BodyHandlers.ofString());
                    
                    return String.format("[%s] %s -> %d",
                        Thread.currentThread().getName(),
                        url,
                        response.statusCode());
                } catch (Exception e) {
                    return String.format("[ERROR] %s -> %s", url, e.getMessage());
                }
            }, executor);
            
            futures.add(future);
        }
        
        // Aguardar todas as requisições
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        
        return futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList());
    }
    
    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) {
        ParallelProxyScraper scraper = new ParallelProxyScraper(10);
        
        List<String> urls = List.of(
            "https://httpbin.org/ip",
            "https://httpbin.org/headers",
            "https://httpbin.org/user-agent",
            "https://httpbin.org/get?query=1",
            "https://httpbin.org/get?query=2",
            "https://httpbin.org/get?query=3"
        );
        
        try {
            List<String> results = scraper.scrapeUrls(urls);
            results.forEach(System.out::println);
        } finally {
            scraper.shutdown();
        }
    }
}

Configuração TLS e SSLContext personalizado

Alguns sites usam certificados não-padronizados ou requerem configurações TLS específicas. O Java usa JSSE (Java Secure Socket Extension) para TLS:

SSLContext personalizado com TrustManager

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLSession;
import java.security.cert.X509Certificate;
import java.net.http.HttpClient;

public class TlsProxyConfig {
    
    // ATENÇÃO: Apenas para desenvolvimento! Não use em produção.
    public static SSLContext createTrustAllContext() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
                public void checkClientTrusted(X509Certificate[] certs, String t) {}
                public void checkServerTrusted(X509Certificate[] certs, String t) {}
            }
        };
        
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        return sslContext;
    }
    
    // Configuração TLS 1.3 para máxima segurança
    public static SSLContext createTls13Context() throws Exception {
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, null, new java.security.SecureRandom());
        return sslContext;
    }
    
    public static HttpClient createTlsClient(SSLContext sslContext) {
        ProxySelector proxySelector = ProxySelector.of(
            new InetSocketAddress("gate.proxyhat.com", 8080));
        
        Authenticator auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                if (getRequestorType() == RequestorType.PROXY) {
                    return new PasswordAuthentication("user-country-US", "sua_senha".toCharArray());
                }
                return null;
            }
        };
        
        return HttpClient.newBuilder()
            .proxy(proxySelector)
            .authenticator(auth)
            .sslContext(sslContext)
            .sslParameters(new SSLParameters(new String[]{"TLSv1.3", "TLSv1.2"}))
            .connectTimeout(Duration.ofSeconds(30))
            .build();
    }
    
    public static void main(String[] args) throws Exception {
        SSLContext tlsContext = createTls13Context();
        HttpClient client = createTlsClient(tlsContext);
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://httpbin.org/ip"))
            .GET()
            .build();
        
        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());
        
        System.out.println(response.body());
    }
}

Configurando TLS no 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 createTlsClient() throws Exception {
        // TrustManager customizado (apenas para desenvolvimento)
        X509TrustManager 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]; }
        };
        
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, new X509TrustManager[]{trustManager}, null);
        
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
            new InetSocketAddress("gate.proxyhat.com", 8080));
        
        return new OkHttpClient.Builder()
            .proxy(proxy)
            .proxyAuthenticator((route, response) -> {
                String credential = Credentials.basic("user-country-US", "sua_senha");
                return response.request().newBuilder()
                    .header("Proxy-Authorization", credential)
                    .build();
            })
            .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
            .hostnameVerifier((hostname, session) -> true)
            .connectTimeout(30, TimeUnit.SECONDS)
            .build();
    }
}

Connection pooling, timeouts e retry policy

Aplicações de produção precisam de configurações robustas:

HttpClient nativo com retry

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RobustHttpClient {
    
    private final HttpClient client;
    private final ExecutorService executor;
    private final int maxRetries;
    private final long retryDelayMs;
    
    public RobustHttpClient(int maxRetries, long retryDelayMs) {
        this.maxRetries = maxRetries;
        this.retryDelayMs = retryDelayMs;
        this.executor = Executors.newFixedThreadPool(10);
        
        ProxySelector proxySelector = ProxySelector.of(
            new InetSocketAddress("gate.proxyhat.com", 8080));
        
        Authenticator auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                if (getRequestorType() == RequestorType.PROXY) {
                    return new PasswordAuthentication("user-country-US", "sua_senha".toCharArray());
                }
                return null;
            }
        };
        
        this.client = HttpClient.newBuilder()
            .proxy(proxySelector)
            .authenticator(auth)
            .executor(executor)
            .connectTimeout(Duration.ofSeconds(30))
            .followRedirects(HttpClient.Redirect.NORMAL)
            .version(HttpClient.Version.HTTP_2)
            .build();
    }
    
    public CompletableFuture<HttpResponse<String>> sendWithRetry(HttpRequest request) {
        return sendWithRetry(request, 0);
    }
    
    private CompletableFuture<HttpResponse<String>> sendWithRetry(HttpRequest request, int attempt) {
        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .exceptionallyCompose(e -> {
                if (attempt < maxRetries) {
                    System.out.println("Tentativa " + (attempt + 1) + " falhou, tentando novamente...");
                    try {
                        Thread.sleep(retryDelayMs * (attempt + 1)); // Backoff exponencial
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        return CompletableFuture.failedFuture(ie);
                    }
                    return sendWithRetry(request, attempt + 1);
                }
                return CompletableFuture.failedFuture(e);
            });
    }
    
    public void shutdown() {
        executor.shutdown();
    }
    
    public static void main(String[] args) throws Exception {
        RobustHttpClient client = new RobustHttpClient(3, 1000);
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://httpbin.org/delay/5"))
            .timeout(Duration.ofSeconds(60))
            .GET()
            .build();
        
        client.sendWithRetry(request)
            .thenAccept(response -> {
                System.out.println("Status: " + response.statusCode());
                System.out.println("Body: " + response.body());
            })
            .exceptionally(e -> {
                System.err.println("Falha após retries: " + e.getMessage());
                return null;
            })
            .join();
        
        client.shutdown();
    }
}

Comparação de clientes HTTP Java

Característica Java HttpClient OkHttp Apache HttpClient Jsoup (fetch)
Versão Java mínima 11+ Java 8+ Java 8+ Java 8+
HTTP/2 Sim Sim Limitado Não
Async/Reactive CompletableFuture Callback Callback Não
Connection Pool Automático Configurável Configurável Não
Proxy Auth Authenticator proxyAuthenticator CredentialsProvider Header manual
Dependências Nenhuma (JDK) okhttp.jar httpclient.jar jsoup.jar
Ideal para Projetos modernos Android/Backend Sistemas legados Scraping simples

Principais pontos

  • Java HttpClient (11+) é a escolha moderna — usa ProxySelector e Authenticator para proxies autenticados.
  • OkHttp oferece API mais intuitiva com proxy() e proxyAuthenticator(), ideal para Android e backends.
  • Jsoup suporta proxies diretamente, mas para scraping robusto combine com OkHttp.
  • Apache HttpClient ainda é usado em sistemas legados — configuração mais verbosa.
  • Geo-targeting é feito via username: user-country-US, user-country-DE-city-berlin.
  • Sessões sticky mantêm o mesmo IP: user-session-abc123.
  • TLS customizado via SSLContext para sites com certificados não-padronizados.
  • Retry com backoff é essencial para scraping resiliente.

Conclusão

Configurar proxies HTTP em Java requer atenção à API específica de cada cliente. O java.net.http.HttpClient nativo é suficiente para a maioria dos casos modernos, enquanto OkHttp oferece mais flexibilidade para Android e ambientes corporativos. Para scraping de HTML, Jsoup é indispensável — mas combine-o com um cliente HTTP robusto para controle de conexões e retries.

Proxies residenciais como os oferecidos pela ProxyHat permitem distribuir requisições entre milhares de IPs reais, evitando rate limits e bloqueios. Configure sessões sticky quando precisar manter consistência, ou use rotação automática para máxima dispersão.

Para mais exemplos de uso, consulte nossa documentação em /pt/use-cases/web-scraping ou confira os locais disponíveis para geo-targeting.

Pronto para começar?

Acesse mais de 50M de IPs residenciais em mais de 148 países com filtragem por IA.

Ver preçosProxies residenciais
← Voltar ao Blog