Las aplicaciones Java corporativas acceden regularmente a API externas, analizan datos o funcionan en redes corporativas aisladas, y en cada uno de estos casos, tarde o temprano surge la cuestión de enrutar el tráfico a través de un servidor proxy. Configurar un proxy en Java parece una tarea sencilla, hasta que te enfrentas a que RestTemplate, OkHttp y WebClient se comportan de manera diferente, y las propiedades del sistema JVM no funcionan en todas partes. En esta guía, encontrarás ejemplos de código concretos, explicaciones de las trampas y recomendaciones sobre qué tipo de proxy elegir para diferentes tareas.
Por qué las aplicaciones Java necesitan proxies
Java sigue siendo uno de los principales lenguajes de desarrollo corporativo. Los servicios de Spring Boot, las arquitecturas de microservicios, las tareas por lotes, los pipelines ETL, todo esto realiza regularmente solicitudes HTTP al mundo exterior. Y en cada uno de estos escenarios, un proxy resuelve una tarea específica:
- Redes corporativas con un proxy obligatorio. En muchas empresas, todo el tráfico saliente pasa a través de un proxy corporativo (Squid, Zscaler, BlueCoat). Sin su configuración, la aplicación simplemente no podrá acceder a Internet.
- Parseo y recopilación de datos. Los servicios que recopilan datos de sitios externos (precios, cotizaciones, noticias) utilizan proxies para eludir límites de tasa y bloqueos por IP.
- Pruebas geográficas. Los equipos de QA verifican cómo se comporta una API o un sitio desde diferentes regiones; un proxy permite emular una solicitud desde el país deseado.
- Eludir restricciones del API de destino. Algunas APIs externas tienen límites en la cantidad de solicitudes desde una sola IP. La rotación de proxies permite distribuir la carga.
- Seguridad y anonimización. Ocultar la IP real de la aplicación al acceder a servicios externos es una práctica estándar para integraciones sensibles.
Dependiendo de la tarea, los enfoques para configurar proxies en Java varían significativamente. Las propiedades globales del sistema JVM son la opción más sencilla, pero no funcionan con algunos clientes HTTP. OkHttp y WebClient requieren una configuración explícita. Analicemos cada caso por separado.
Configuración global a través de propiedades del sistema JVM
La forma más rápida de dirigir todo el tráfico HTTP de una aplicación Java a través de un proxy es utilizar propiedades del sistema JVM. Esto funciona para el estándar HttpURLConnection y muchas bibliotecas que lo utilizan bajo el capó.
Puedes establecer un proxy de varias maneras:
1. A través de argumentos JVM al iniciar:
java -Dhttp.proxyHost=proxy.example.com \
-Dhttp.proxyPort=8080 \
-Dhttps.proxyHost=proxy.example.com \
-Dhttps.proxyPort=8080 \
-Dhttp.nonProxyHosts="localhost|127.0.0.1|*.internal.corp" \
-jar myapp.jar
2. A través del código de la aplicación (por ejemplo, en el método main o en @PostConstruct):
System.setProperty("http.proxyHost", "proxy.example.com");
System.setProperty("http.proxyPort", "8080");
System.setProperty("https.proxyHost", "proxy.example.com");
System.setProperty("https.proxyPort", "8080");
// Excepciones — hosts que no deben ir a través del proxy
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1|*.internal.corp");
3. A través del archivo application.properties en Spring Boot (aplicándolo posteriormente a través de @PostConstruct):
# application.properties proxy.host=proxy.example.com proxy.port=8080 proxy.username=user proxy.password=secret
⚠️ Importante saber
Las propiedades del sistema JVM solo funcionan para HttpURLConnection. OkHttp, Apache HttpClient y Reactor Netty (WebClient) ignoran estas propiedades; para ellos se necesita una configuración explícita, que se describe a continuación.
Si el proxy requiere autenticación, utiliza Authenticator:
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication("username", "password".toCharArray());
}
return null;
}
});
Proxy en Spring Boot: RestTemplate y Apache HttpClient
RestTemplate es el cliente HTTP sincrónico estándar en Spring. Por defecto, utiliza SimpleClientHttpRequestFactory, que se basa en HttpURLConnection y lee las propiedades del sistema JVM. Pero para una configuración más flexible (timeouts, pool de conexiones, proxy con autenticación), se recomienda usar Apache HttpClient como backend.
Dependencia en Maven:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
Configuración de RestTemplate con proxy a través de Apache HttpClient 5:
@Configuration
public class RestTemplateConfig {
@Value("${proxy.host}")
private String proxyHost;
@Value("${proxy.port}")
private int proxyPort;
@Value("${proxy.username:}")
private String proxyUsername;
@Value("${proxy.password:}")
private String proxyPassword;
@Bean
public RestTemplate restTemplate() {
HttpHost proxy = new HttpHost("http", proxyHost, proxyPort);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
if (!proxyUsername.isEmpty()) {
credentialsProvider.setCredentials(
new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUsername, proxyPassword.toCharArray())
);
}
CloseableHttpClient httpClient = HttpClients.custom()
.setProxy(proxy)
.setDefaultCredentialsProvider(credentialsProvider)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}
}
Esta configuración permite extraer los parámetros del proxy en application.properties o variables de entorno, lo cual es crítico para entornos de producción/etapas/desarrollo con diferentes proxies.
Si necesitas que parte de las solicitudes pase a través del proxy y parte directamente, crea dos beans separados de RestTemplate con diferentes nombres y utiliza @Qualifier para inyectar el necesario.
Proxy en Spring WebClient (stack reactivo)
WebClient es el cliente HTTP reactivo de Spring, construido sobre Reactor Netty. No lee las propiedades del sistema JVM para proxies; la configuración se realiza a través de ProxyProvider al crear el cliente.
Dependencia (incluida en spring-boot-starter-webflux):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Configuración de WebClient con proxy HTTP:
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.HTTP)
.host("proxy.example.com")
.port(8080)
.username("user")
.password(user -> "password")
);
ClientHttpConnector connector =
new ReactorClientHttpConnector(httpClient);
return WebClient.builder()
.clientConnector(connector)
.build();
}
}
Para proxies SOCKS5 en WebClient:
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.SOCKS5)
.host("socks5.proxy.example.com")
.port(1080)
.username("user")
.password(user -> "password")
);
💡 Consejo de configuración
En aplicaciones reactivas, a menudo es necesario crear múltiples instancias de WebClient con diferentes proxies para diferentes servicios externos. Utiliza WebClient.Builder como un bean prototipo y sobreescribe la configuración a través de .mutate().
Configuración de proxy en OkHttp
OkHttp es una biblioteca HTTP popular que utilizan muchas aplicaciones Java y Android, así como algunas integraciones de Spring (por ejemplo, Feign con backend OkHttp). Tiene su propio mecanismo de configuración de proxy y tampoco lee las propiedades del sistema JVM.
Configuración básica de proxy HTTP/HTTPS en OkHttp:
import okhttp3.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
// Creamos el proxy
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 8080)
);
// Autenticación del proxy
Authenticator proxyAuthenticator = (route, response) -> {
String credential = Credentials.basic("username", "password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
};
// Construimos el cliente
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
// Ejemplo de solicitud
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
Integración de OkHttp con Spring Boot a través de un bean:
@Configuration
@ConditionalOnProperty(name = "proxy.enabled", havingValue = "true")
public class OkHttpConfig {
@Value("${proxy.host}")
private String proxyHost;
@Value("${proxy.port}")
private int proxyPort;
@Bean
public OkHttpClient okHttpClient() {
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort)
);
return new OkHttpClient.Builder()
.proxy(proxy)
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
.build();
}
}
Presta atención a @ConditionalOnProperty — esto permite habilitar el proxy solo en los entornos necesarios a través de la configuración, sin cambiar el código.
Si utilizas OkHttp junto con proxies residenciales para el análisis de datos, es importante configurar correctamente el pool de conexiones. Los proxies residenciales a menudo tienen una latencia más alta en comparación con los de centros de datos, así que aumenta los timeouts y reduce el tamaño del pool para no agotar las conexiones disponibles.
Proxy SOCKS5 en Java: características y ejemplos
SOCKS5 es un protocolo de nivel más bajo en comparación con el proxy HTTP. Funciona a nivel de transporte y admite cualquier protocolo (HTTP, HTTPS, FTP, etc.), así como la resolución DNS del lado del proxy. Para aplicaciones Java, SOCKS5 es especialmente útil cuando se necesita proxy no solo HTTP, sino también otras conexiones TCP.
SOCKS5 a través de propiedades del sistema JVM:
System.setProperty("socksProxyHost", "socks5.proxy.example.com");
System.setProperty("socksProxyPort", "1080");
System.setProperty("java.net.socks.username", "user");
System.setProperty("java.net.socks.password", "password");
SOCKS5 en OkHttp (a través de java.net.Proxy):
Proxy socks5Proxy = new Proxy(
Proxy.Type.SOCKS,
new InetSocketAddress("socks5.proxy.example.com", 1080)
);
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.build();
Una característica importante de SOCKS5 y DNS: Por defecto, Java resuelve DNS localmente y transmite la dirección IP a través del proxy. Esto puede revelar la verdadera solicitud DNS. Para que la resolución DNS también pase a través del proxy, utiliza el siguiente enfoque con OkHttp:
// DNS personalizado que fuerza la resolución a través de SOCKS5
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.dns(hostname -> {
// Devolvemos el host no resuelto — OkHttp lo pasará al proxy
return Collections.singletonList(InetAddress.getByName(hostname));
})
.build();
Cuándo elegir SOCKS5 vs HTTP-proxy
| Criterio | HTTP-proxy | SOCKS5-proxy |
|---|---|---|
| Protocolos | HTTP/HTTPS | Cualquier TCP/UDP |
| DNS a través del proxy | Depende de la implementación | Soportado |
| Compatibilidad con Java | Nativa | Nativa (java.net.Proxy.Type.SOCKS) |
| Uso | Puertas de enlace corporativas, web scraping | Anonimización, protocolos no estándar |
Rotación de proxies en aplicaciones Java
Si una aplicación realiza un gran número de solicitudes a un solo recurso, el uso de una sola IP de proxy rápidamente conducirá a un bloqueo. La rotación de proxies — cambiar entre diferentes direcciones IP — resuelve este problema. En Java, esto se implementa de varias maneras.
Opción 1: Rotación a través de un endpoint de proxy rotativo
El enfoque más sencillo es utilizar un proveedor de proxies con un único endpoint que cambia automáticamente la IP en cada solicitud o a través de un intervalo establecido. Solo indicas un host y un puerto, y la rotación ocurre del lado del proveedor. No es necesario escribir ningún código para la rotación.
Opción 2: Rotación del lado de la aplicación a través de una lista de proxies
@Component
public class ProxyRotator {
private final List<ProxyConfig> proxies;
private final AtomicInteger counter = new AtomicInteger(0);
public ProxyRotator(@Value("${proxy.list}") List<String> proxyList) {
this.proxies = proxyList.stream()
.map(this::parseProxy)
.collect(Collectors.toList());
}
public OkHttpClient getClientWithNextProxy() {
int index = counter.getAndIncrement() % proxies.size();
ProxyConfig config = proxies.get(index);
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(config.getHost(), config.getPort())
);
return new OkHttpClient.Builder()
.proxy(proxy)
.build();
}
private ProxyConfig parseProxy(String proxyStr) {
// Parseamos la cadena en formato "host:puerto" o "usuario:contraseña@host:puerto"
String[] parts = proxyStr.split(":");
return new ProxyConfig(parts[0], Integer.parseInt(parts[1]));
}
}
Opción 3: Rotación a través de ProxySelector
Java proporciona un mecanismo incorporado ProxySelector, que permite seleccionar dinámicamente proxies en función de la URL de destino. Esto funciona con HttpURLConnection y Apache HttpClient:
ProxySelector.setDefault(new ProxySelector() {
private final List<Proxy> proxyPool = Arrays.asList(
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy1.example.com", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy2.example.com", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
);
private final AtomicInteger idx = new AtomicInteger(0);
@Override
public List<Proxy> select(URI uri) {
// Se puede agregar lógica de selección por dominio
int i = idx.getAndIncrement() % proxyPool.size();
return Collections.singletonList(proxyPool.get(i));
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// Lógica para manejar un proxy no disponible
log.warn("Proxy fallido: {}", sa);
}
});
Qué tipo de proxy elegir para tareas Java
La elección del tipo de proxy depende de la tarea específica. Para aplicaciones Java corporativas, generalmente se consideran tres tipos principales:
| Tipo de proxy | Velocidad | Anonimato | Mejor para |
|---|---|---|---|
| Proxies de centros de datos | Alta | Media | Scraping de datos abiertos, pruebas de API, alta carga |
| Proxies residenciales | Media | Alta | Scraping de sitios protegidos, pruebas geográficas, eludir bloqueos |
| Proxies móviles | Media | Máxima | Integraciones con APIs móviles, servicios con estrictas protecciones antifraude |
Recomendaciones prácticas según tipos de tareas:
- Puerta de enlace corporativa (Squid, Zscaler): El tipo de proxy lo determina el departamento de IT. Utiliza proxies HTTP con autenticación a través de Apache HttpClient o propiedades del sistema JVM.
- Scraping de datos públicos (noticias, cotizaciones, APIs abiertas): Los proxies de centros de datos son la mejor opción en términos de velocidad y costo.
- Scraping de e-commerce (Wildberries, Ozon, Amazon): Proxies residenciales con rotación. Estas plataformas bloquean activamente las IP de centros de datos.
- Pruebas geográficas (verificación de localización, precios por regiones): Proxies residenciales con selección de país/ciudad.
- Integración con servicios y aplicaciones móviles: Proxies móviles — las IP de redes móviles reales imitan mejor a los usuarios reales.
Errores comunes al configurar proxies en Java y cómo solucionarlos
Incluso los desarrolladores experimentados enfrentan los mismos problemas al configurar proxies en Java. Analicemos los más comunes.
1. Proxy configurado, pero el tráfico aún va directamente
Causa: Has establecido propiedades del sistema JVM, pero estás utilizando OkHttp o Reactor Netty (WebClient), que las ignoran.
Solución: Configura el proxy explícitamente en la configuración del cliente HTTP específico, como se muestra en las secciones anteriores.
2. javax.net.ssl.SSLHandshakeException al HTTPS a través de un proxy
Causa: El proxy corporativo realiza inspección SSL (MITM), reemplazando el certificado. Java no confía en el CA corporativo.
Solución: Importa el certificado corporativo en el truststore de Java:
keytool -import -trustcacerts \ -alias corporate-ca \ -file corporate-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit
3. java.net.ConnectException: Conexión rechazada
Causas: Host o puerto del proxy incorrectos; el servidor proxy no está disponible; el firewall bloquea la conexión.
Diagnóstico:
# Verificamos la disponibilidad del proxy curl -x http://proxy.example.com:8080 https://httpbin.org/ip # O a través de telnet telnet proxy.example.com 8080
4. El proxy funciona para HTTP, pero no para HTTPS
Causa: Solo se han establecido las propiedades http.proxyHost/Port, pero no https.proxyHost/Port.
Solución: Establece ambos conjuntos de propiedades. Para HTTPS a través de un proxy HTTP se utiliza el método de tunelización CONNECT; asegúrate de que el proxy lo soporte.
5. 407 Proxy Authentication Required
Causa: El proxy requiere autenticación, pero no está configurada.
Solución: Agrega Authenticator para las propiedades del sistema JVM o configura CredentialsProvider para Apache HttpClient / proxyAuthenticator para OkHttp (ejemplos arriba).
6. Fuga de memoria al rotar proxies
Causa: Se crea una nueva instancia de OkHttpClient en cada solicitud. Cada cliente mantiene su propio pool de hilos y conexiones.
Solución: Reutiliza los clientes. Crea un pool de varios clientes (uno por proxy) y rótalos, en lugar de crear nuevos.
7. Activar el registro de depuración para diagnóstico
# application.properties — activamos los logs para diagnosticar el proxy logging.level.org.apache.http=DEBUG logging.level.org.apache.http.wire=DEBUG logging.level.reactor.netty.http.client=DEBUG # Para JVM — propiedad del sistema -Djava.net.debug=all
Conclusión
Configurar un proxy en aplicaciones Java requiere entender qué cliente HTTP se utiliza bajo el capó. Las propiedades del sistema JVM son un buen comienzo para HttpURLConnection, pero OkHttp, WebClient y Apache HttpClient requieren configuración explícita. Para aplicaciones corporativas, se recomienda extraer los parámetros del proxy en application.properties y utilizar perfiles de Spring para diferentes entornos. La rotación de proxies es mejor delegarla a un proveedor con un endpoint rotativo; es más fácil y confiable que implementarlo por tu cuenta.
Si tu aplicación Java se dedica a analizar datos de plataformas protegidas o requiere alta anonimidad en las solicitudes externas, te recomendamos considerar proxies residenciales — tienen IP reales de usuarios domésticos, lo que reduce significativamente la probabilidad de bloqueos por parte de sistemas antifraude. Para scraping de datos abiertos de alta carga, los proxies de centros de datos son óptimos, ya que ofrecen la máxima velocidad a un costo menor de tráfico.
```