Java proxy authentication: 407 with the right password
Java turns off password authentication for HTTPS proxy tunnels by default. Your Authenticator is never asked, and a correct login gets a 407. One property fixes it.
In short
Start Java with -Djdk.http.auth.tunneling.disabledSchemes= (empty), or set the property to "" in code before the first request. Then a proxy Authenticator works for https:// URLs. Without it, every login gets 407.
For an https:// URL, Java asks the proxy to open a tunnel with CONNECT. The proxy answers 407 and asks for a login with the Basic scheme. Since Java 8u111, Java refuses to send Basic credentials for tunnels: the scheme is listed in the system property jdk.http.auth.tunneling.disabledSchemes. So your Authenticator is never called, and the 407 is final.
We ran the same program twice, once with Java’s defaults and once with the property cleared, against our gateway:
The default exists so that a Basic password never travels in clear text to a proxy without you deciding to allow it. The connection from your program to an HTTP proxy port isn’t encrypted, so clearing the property means trusting the network between you and the proxy. For http:// URLs (no tunnel) Java never disabled Basic; the same login works there.
Every message, reproduced
java.net.http.HttpClient and HttpURLConnection on Java 21, against via.proxies.vision:9000.
| Setting | Login | HttpClient | HttpURLConnection |
|---|---|---|---|
| default | correct | response 407, body null | IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 …" |
| property cleared | correct | 200 | 200 |
| property cleared | wrong password | IOException: too many authentication attempts. Limit: 3 | — |
| property cleared | -country-usa | IOException: Tunnel failed, got: 403 | — |
| property cleared | Chicago in Germany | IOException: Tunnel failed, got: 465 | — |
HttpClient doesn’t throw on 407
With the defaults, client.send() returned normally with statusCode() == 407 and an empty body. Code that only catches exceptions will happily parse nothing. Check the status before reading the body.
A real wrong password
Once Java may send the login, a wrong one gives too many authentication attempts. Limit: 3: Java asked your Authenticator three times and got the same rejected answer. That message, not the plain 407, means the credentials are wrong.
403: a value in the username
Targeting goes into the proxy username. Our gateway refuses values it can’t read, such as a three-letter country code or a session label on the rotating port: Tunnel failed, got: 403. A key it doesn’t know, such as -cc-DE, is ignored without an error.
465: no exit
The login and the username are fine, but no residential exit matches every filter right now. Drop the narrowest one, usually the city. The same codes in other clients: curl, Python requests.
A setup that works
HttpClient with a proxy Authenticator, the property cleared in code. Ran unchanged on Java 21 and 17 with java ProxyDemo.java.
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 ProxyDemo {
public static void main(String[] args) throws Exception {
// Java refuses Basic proxy auth for HTTPS tunnels unless this is cleared,
// before the first request. (Or start java with -Djdk.http.auth.tunneling.disabledSchemes=)
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
String user = System.getenv("PROXY_USER") + "-country-de";
String pass = System.getenv("PROXY_PASS");
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("via.proxies.vision", 9000)))
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// Answer the proxy only, never a website asking for a login.
return getRequestorType() == RequestorType.PROXY
? new PasswordAuthentication(user, pass.toCharArray())
: null;
}
})
.connectTimeout(Duration.ofSeconds(30))
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create("https://ipinfo.io/json")).build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// A 407 here is the proxy's answer, not the website's: check the login.
System.out.println(response.statusCode() + " " + response.body());
}
}200 {
"ip": "94.134.182.246",
"city": "Karlsruhe",
"country": "DE",
…Each new connection through port 9000 gets a new exit. For the same IP across connections, put a session label into the username and use port 10000: USER-country-de-session-job1-lifetime-10.
Why does Java get 407 from the proxy when the password is correct?
How do I set jdk.http.auth.tunneling.disabledSchemes?
Why does HttpClient return 407 instead of throwing?
What does 'too many authentication attempts. Limit: 3' mean?
Keep reading
aiohttp Proxy Errors: ClientHttpProxyError 407, SOCKS5 and trust_env
aiohttp's ClientHttpProxyError explained, why its message leaks your proxy password, socks5:// without aiohttp-socks, and rotation.
6 min read · #python#errors
curl: (56) CONNECT tunnel failed, response 407 — and git's Proxy CONNECT aborted
What curl's 'CONNECT tunnel failed, response 407' means, every curl proxy exit code we hit, and why git fails through a proxy that works in curl.
6 min read · #errors#authentication
Axios HTTPS Proxy Not Working: httpsAgent, proxy: false and 407
Why an axios proxy silently doesn't apply, why HTTPS_PROXY beats your agent, and how to tell the proxy's 403 from the website's.
6 min read · #node.js#errors
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.