Java's HttpClient doesn't resolve localhost to IPv6

I was just stuck at this problem for half an hour. The issue was a bit difficult to figure out.

I have this Express server (Node.js) running:

var hmr = http.createServer(app);
hmr.listen(8090, "localhost", function () {
    console.log('Listening on %j', hmr.address());
});

After running it, the logs show:

Listening on {"address":"::1","family":"IPv6","port":8090}

Ok, nothing suspicious about it. To be fair, nothing was broken. This piece of code was working fine with curl or any other HTTP client.

Then, I have a Java code that uses HttpClient to connect to this Express server:

var client = HttpClient.newHttpClient();
var httpRequest = HttpRequest
  .newBuilder()
  .uri(URI.create("http://localhost:8090/something"))
  .GET()
  .build();
client.send(httpRequest, HttpResponse.BodyHandlers.discarding());

And the code fails with:

[error] java.net.ConnectException
[error] 	at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:951)
[error] 	at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
[error] 	at tanin.ejwf.Main.main(Main.java:30)
[error] 	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
[error] 	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
[error] Caused by: java.net.ConnectException
[error] 	at java.net.http/jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1028)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:227)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.checkRetryConnect(PlainHttpConnection.java:280)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$2(PlainHttpConnection.java:238)
[error] 	at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
[error] 	at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
[error] 	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
[error] 	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
[error] 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[error] 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
[error] 	at java.base/java.lang.Thread.run(Thread.java:1583)
[error] Caused by: java.nio.channels.ClosedChannelException
[error] 	at java.base/sun.nio.ch.SocketChannelImpl.ensureOpen(SocketChannelImpl.java:202)
[error] 	at java.base/sun.nio.ch.SocketChannelImpl.beginConnect(SocketChannelImpl.java:786)
[error] 	at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:874)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:210)
[error] 	at java.base/java.security.AccessController.doPrivileged(AccessController.java:571)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:212)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.checkRetryConnect(PlainHttpConnection.java:280)
[error] 	at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$2(PlainHttpConnection.java:238)
[error] 	at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
[error] 	at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
[error] 	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
[error] 	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
[error] 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[error] 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
[error] 	at java.base/java.lang.Thread.run(Thread.java:1583)

It was a simple ConnectException. curl works just fine. A browser works just fine connecting to http://localhost:8090/something.

The HttpClient works with any other URL like https://webhook.site.

Then, I noticed the log line from Express:

Listening on {"address":"::1","family":"IPv6","port":8090}

It suspiciously only bound to IPv6. So, I changed the URL in Java from http://localhost:8090 to http://[::1]:8090. Then, surprise! It worked.

Java's HttpClient, for some reason, doesn't resolve localhost to both IPv4 and IPv6 like any other reasonable http clients.

The fix is to bind the Express server to 127.0.0.1, not localhost, and that makes it work with all HTTP clients including Java's HTTP client.

Subscribe to tanin

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe