Корпоративные Java-приложения регулярно обращаются к внешним API, парсят данные или работают в изолированных корпоративных сетях — и в каждом из этих случаев рано или поздно встаёт вопрос маршрутизации трафика через прокси-сервер. Настройка прокси в Java выглядит простой задачей, пока не сталкиваешься с тем, что RestTemplate, OkHttp и WebClient ведут себя по-разному, а системные свойства JVM работают не везде. В этом руководстве — конкретные примеры кода, объяснение подводных камней и рекомендации по выбору типа прокси под разные задачи.
Зачем Java-приложениям нужны прокси
Java остаётся одним из главных языков корпоративной разработки. Spring Boot-сервисы, микросервисные архитектуры, batch-задачи, ETL-пайплайны — всё это регулярно делает HTTP-запросы во внешний мир. И в каждом из этих сценариев прокси решает конкретную задачу:
- Корпоративные сети с обязательным прокси-шлюзом. Во многих компаниях весь исходящий трафик проходит через корпоративный прокси (Squid, Zscaler, BlueCoat). Без его настройки приложение просто не выйдет в интернет.
- Парсинг и сбор данных. Сервисы, которые собирают данные с внешних сайтов (цены, котировки, новости), используют прокси для обхода rate-limit и блокировок по IP.
- Гео-тестирование. QA-команды проверяют, как API или сайт ведёт себя из разных регионов — прокси позволяет эмулировать запрос из нужной страны.
- Обход ограничений на стороне целевого API. Некоторые внешние API имеют лимиты на количество запросов с одного IP. Ротация прокси позволяет распределить нагрузку.
- Безопасность и анонимизация. Скрытие реального IP приложения при обращении к внешним сервисам — стандартная практика для чувствительных интеграций.
В зависимости от задачи подходы к настройке прокси в Java существенно различаются. Глобальные системные свойства JVM — самый простой вариант, но он не работает с некоторыми HTTP-клиентами. OkHttp и WebClient требуют явной конфигурации. Разберём каждый случай по порядку.
Глобальная настройка через системные свойства JVM
Самый быстрый способ направить весь HTTP-трафик Java-приложения через прокси — использовать системные свойства JVM. Это работает для стандартного HttpURLConnection и многих библиотек, которые используют его под капотом.
Задать прокси можно несколькими способами:
1. Через аргументы JVM при запуске:
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. Через код приложения (например, в методе main или в @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");
// Исключения — хосты, которые не должны идти через прокси
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1|*.internal.corp");
3. Через файл application.properties в Spring Boot (с последующим применением через @PostConstruct):
# application.properties proxy.host=proxy.example.com proxy.port=8080 proxy.username=user proxy.password=secret
⚠️ Важно знать
Системные свойства JVM работают только для HttpURLConnection. OkHttp, Apache HttpClient и Reactor Netty (WebClient) игнорируют эти свойства — для них нужна явная конфигурация, описанная ниже.
Если прокси требует аутентификации, используйте Authenticator:
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication("username", "password".toCharArray());
}
return null;
}
});
Прокси в Spring Boot: RestTemplate и Apache HttpClient
RestTemplate — стандартный синхронный HTTP-клиент в Spring. По умолчанию он использует SimpleClientHttpRequestFactory, который опирается на HttpURLConnection и читает системные свойства JVM. Но для более гибкой настройки (таймауты, пул соединений, прокси с аутентификацией) рекомендуется использовать Apache HttpClient в качестве бэкенда.
Зависимость в Maven:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
Конфигурация RestTemplate с прокси через 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);
}
}
Такая конфигурация позволяет вынести параметры прокси в application.properties или переменные окружения, что критично для prod/staging/dev окружений с разными прокси.
Если нужно, чтобы часть запросов шла через прокси, а часть — напрямую, создайте два отдельных бина RestTemplate с разными именами и используйте @Qualifier для инжекции нужного.
Прокси в Spring WebClient (реактивный стек)
WebClient — реактивный HTTP-клиент Spring, построенный на Reactor Netty. Он не читает системные свойства JVM для прокси — настройка делается через ProxyProvider при создании клиента.
Зависимость (входит в spring-boot-starter-webflux):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Конфигурация WebClient с 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();
}
}
Для SOCKS5-прокси в WebClient:
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.SOCKS5)
.host("socks5.proxy.example.com")
.port(1080)
.username("user")
.password(user -> "password")
);
💡 Совет по конфигурации
В реактивных приложениях часто нужно создавать несколько экземпляров WebClient с разными прокси для разных внешних сервисов. Используйте WebClient.Builder как прототип-бин и переопределяйте настройки через .mutate().
Настройка прокси в OkHttp
OkHttp — популярная HTTP-библиотека, которую используют многие Java и Android-приложения, а также некоторые Spring-интеграции (например, Feign с OkHttp-бэкендом). Она имеет собственный механизм настройки прокси и также не читает системные свойства JVM.
Базовая настройка HTTP/HTTPS прокси в OkHttp:
import okhttp3.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
// Создаём прокси
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 8080)
);
// Аутентификация прокси
Authenticator proxyAuthenticator = (route, response) -> {
String credential = Credentials.basic("username", "password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
};
// Собираем клиент
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
// Пример запроса
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());
}
Интеграция OkHttp с Spring Boot через бин:
@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();
}
}
Обратите внимание на @ConditionalOnProperty — это позволяет включать прокси только в нужных окружениях через конфигурацию, не меняя код.
Если вы используете OkHttp вместе с резидентными прокси для парсинга данных, важно правильно настроить пул соединений. Резидентные прокси часто имеют более высокую латентность по сравнению с датацентровыми, поэтому увеличьте таймауты и уменьшите размер пула, чтобы не исчерпать доступные соединения.
SOCKS5-прокси в Java: особенности и примеры
SOCKS5 — более низкоуровневый протокол по сравнению с HTTP-прокси. Он работает на транспортном уровне и поддерживает любые протоколы (HTTP, HTTPS, FTP и др.), а также DNS-резолвинг на стороне прокси. Для Java-приложений SOCKS5 особенно полезен, когда нужно проксировать не только HTTP, но и другие TCP-соединения.
SOCKS5 через системные свойства 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 в OkHttp (через 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();
Важная особенность SOCKS5 и DNS: По умолчанию Java резолвит DNS локально, а через прокси передаёт уже IP-адрес. Это может раскрыть реальный DNS-запрос. Чтобы DNS-резолвинг тоже шёл через прокси, используйте следующий подход с OkHttp:
// Кастомный DNS, который форсирует резолвинг через SOCKS5
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.dns(hostname -> {
// Возвращаем нерезолвленный хост — OkHttp передаст его прокси
return Collections.singletonList(InetAddress.getByName(hostname));
})
.build();
Когда выбирать SOCKS5 vs HTTP-прокси
| Критерий | HTTP-прокси | SOCKS5-прокси |
|---|---|---|
| Протоколы | HTTP/HTTPS | Любые TCP/UDP |
| DNS через прокси | Зависит от реализации | Поддерживается |
| Совместимость с Java | Нативная | Нативная (java.net.Proxy.Type.SOCKS) |
| Использование | Корпоративные шлюзы, веб-парсинг | Анонимизация, нестандартные протоколы |
Ротация прокси в Java-приложениях
Если приложение делает большое количество запросов к одному ресурсу, использование одного прокси-IP быстро приведёт к блокировке. Ротация прокси — переключение между разными IP-адресами — решает эту проблему. В Java это реализуется несколькими способами.
Вариант 1: Ротация через rotating proxy endpoint
Самый простой подход — использовать прокси-провайдера с единым endpoint, который автоматически меняет IP при каждом запросе или через заданный интервал. Вы указываете один хост и порт, а ротация происходит на стороне провайдера. Никакого кода для ротации писать не нужно.
Вариант 2: Ротация на стороне приложения через список прокси
@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) {
// Парсим строку вида "host:port" или "user:pass@host:port"
String[] parts = proxyStr.split(":");
return new ProxyConfig(parts[0], Integer.parseInt(parts[1]));
}
}
Вариант 3: Ротация через ProxySelector
Java предоставляет встроенный механизм ProxySelector, который позволяет динамически выбирать прокси на основе URL назначения. Это работает с HttpURLConnection и 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) {
// Можно добавить логику выбора по домену
int i = idx.getAndIncrement() % proxyPool.size();
return Collections.singletonList(proxyPool.get(i));
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// Логика обработки недоступного прокси
log.warn("Proxy failed: {}", sa);
}
});
Какой тип прокси выбрать для Java-задач
Выбор типа прокси зависит от конкретной задачи. Для корпоративных Java-приложений обычно рассматривают три основных типа:
| Тип прокси | Скорость | Анонимность | Лучше всего для |
|---|---|---|---|
| Датацентровые | Высокая | Средняя | Парсинг открытых данных, тестирование API, высокая нагрузка |
| Резидентные | Средняя | Высокая | Парсинг защищённых сайтов, гео-тестирование, обход блокировок |
| Мобильные | Средняя | Максимальная | Интеграции с мобильными API, сервисы с жёсткой антифрод-защитой |
Практические рекомендации по типам задач:
- Корпоративный шлюз (Squid, Zscaler): Тип прокси определяет IT-отдел. Используйте HTTP-прокси с аутентификацией через Apache HttpClient или системные свойства JVM.
- Парсинг публичных данных (новости, котировки, открытые API): Датацентровые прокси — оптимальный выбор по соотношению скорости и стоимости.
- Парсинг e-commerce (Wildberries, Ozon, Amazon): Резидентные прокси с ротацией. Эти платформы активно блокируют датацентровые IP.
- Гео-тестирование (проверка локализации, цен по регионам): Резидентные прокси с выбором страны/города.
- Интеграция с мобильными сервисами и приложениями: Мобильные прокси — IP из реальных мобильных сетей лучше всего имитируют реальных пользователей.
Частые ошибки при настройке прокси в Java и как их исправить
Даже опытные разработчики сталкиваются с одними и теми же проблемами при настройке прокси в Java. Разберём самые распространённые.
1. Прокси настроен, но трафик всё равно идёт напрямую
Причина: Вы задали системные свойства JVM, но используете OkHttp или Reactor Netty (WebClient), которые их игнорируют.
Решение: Настройте прокси явно в конфигурации конкретного HTTP-клиента, как показано в разделах выше.
2. javax.net.ssl.SSLHandshakeException при HTTPS через прокси
Причина: Корпоративный прокси делает SSL-inspection (MITM), подменяя сертификат. Java не доверяет корпоративному CA.
Решение: Импортируйте корпоративный сертификат в Java truststore:
keytool -import -trustcacerts \ -alias corporate-ca \ -file corporate-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit
3. java.net.ConnectException: Connection refused
Причины: Неверный хост или порт прокси; прокси-сервер недоступен; firewall блокирует соединение.
Диагностика:
# Проверяем доступность прокси curl -x http://proxy.example.com:8080 https://httpbin.org/ip # Или через telnet telnet proxy.example.com 8080
4. Прокси работает для HTTP, но не для HTTPS
Причина: Заданы только свойства http.proxyHost/Port, но не https.proxyHost/Port.
Решение: Задайте оба набора свойств. Для HTTPS через HTTP-прокси используется метод CONNECT-туннелирования — убедитесь, что прокси его поддерживает.
5. 407 Proxy Authentication Required
Причина: Прокси требует аутентификации, но она не настроена.
Решение: Добавьте Authenticator для системных свойств JVM или настройте CredentialsProvider для Apache HttpClient / proxyAuthenticator для OkHttp (примеры выше).
6. Утечка памяти при ротации прокси
Причина: При каждом запросе создаётся новый экземпляр OkHttpClient. Каждый клиент держит свой пул потоков и соединений.
Решение: Переиспользуйте клиенты. Создавайте пул из нескольких клиентов (по одному на прокси) и ротируйте их, а не создавайте новые.
7. Включение отладочного логирования для диагностики
# application.properties — включаем логи для диагностики прокси logging.level.org.apache.http=DEBUG logging.level.org.apache.http.wire=DEBUG logging.level.reactor.netty.http.client=DEBUG # Для JVM — системное свойство -Djava.net.debug=all
Заключение
Настройка прокси в Java-приложениях требует понимания того, какой HTTP-клиент используется под капотом. Системные свойства JVM — быстрый старт для HttpURLConnection, но OkHttp, WebClient и Apache HttpClient требуют явной конфигурации. Для корпоративных приложений рекомендуется выносить параметры прокси в application.properties и использовать профили Spring для разных окружений. Ротацию прокси лучше делегировать провайдеру с rotating endpoint — это проще и надёжнее, чем реализовывать самостоятельно.
Если ваше Java-приложение занимается парсингом данных с защищённых платформ или требует высокой анонимности при внешних запросах, рекомендуем рассмотреть резидентные прокси — они имеют реальные IP домашних пользователей, что существенно снижает вероятность блокировок со стороны антифрод-систем. Для высоконагруженного парсинга открытых данных оптимальны прокси дата-центров — они обеспечивают максимальную скорость при меньшей стоимости трафика.