If you ever needed to request HTTP resources with Java, you probably came across several solutions put together from a surprising number of lines. And you probably ended up with using a third party library to achieve your goal in a reasonable manner.
Good news: besides Java 9 modules, the next JDK version comes with a brand new HTTP client, and it not only brings support for HTTP/2, but also a sleek and comprehensive API. Let’s have a closer look at the new features.
HTTP/2 brings awesome new features to the protocol, including
We’ll focus on the HTTP client here, for in-depth information about HTTP/2 you should watch Fabian’s Devoxx talk about HTTP/2.
It is important to notice that the HTTP client will be delivered as an incubator module with JDK 9. This has a few implications:
jdk.incubator.httpclient
.java.httpclient
in JDK 10.Basically, there are three classes involved when communicating via HTTP: a HttpClient
will be used to send HttpRequest
s and receive HttpResponse
s. Good basis for a comprehensible API, right? Let’s see:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://labs.consol.de/"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());
Beautiful, isn’t it? No InputStream
and Reader
involved - instead, the API offers a BodyHandler
which allows us to read the String
directly from the response. We’ll see other BodyHandler
s below.
While HttpClient
, HttpRequest
and HttpResponse
are the main actors in our HTTP communication, we will mostly be working with builders to configure them. The builders provide a concise and chainable API.
We can obtain an instance of HttpRequest.Builder
by calling HttpRequest.newBuilder()
, as we did above. We will use it to configure everything related to a specific request. Let’s have a look at its most important methods:
// HttpRequest.Builder
public abstract static class Builder {
// note: some methods left out for the sake of brevity
public abstract Builder uri(URI uri);
public abstract Builder version(HttpClient.Version version);
public abstract Builder header(String name, String value);
public abstract Builder timeout(Duration duration);
public abstract Builder GET();
public abstract Builder POST(BodyProcessor body);
public abstract Builder PUT(BodyProcessor body);
public abstract Builder DELETE(BodyProcessor body);
public abstract HttpRequest build();
}
Pretty self-explaining, right? Just chain method calls until your request is fully configured, then call build()
to get your HttpRequest
instance. You can read up about the details in the API docs of HttpRequest.Builder.
As with HttpRequest
, we can use HttpClient.newBuilder()
to obtain an instance of HttpClient.Builder
. It provides an API to configure some more generic stuff about our connection. Again, let’s have a look at its methods:
// HttpClient.Builder
public abstract static class Builder {
public abstract Builder cookieManager(CookieManager cookieManager);
public abstract Builder sslContext(SSLContext sslContext);
public abstract Builder sslParameters(SSLParameters sslParameters);
public abstract Builder executor(Executor executor);
public abstract Builder followRedirects(Redirect policy);
public abstract Builder version(HttpClient.Version version);
public abstract Builder priority(int priority);
public abstract Builder proxy(ProxySelector selector);
public abstract Builder authenticator(Authenticator a);
public abstract HttpClient build();
}
Awesome! Again, the API is self-explaining enough to just give it a go and let your IDE’s auto completion guide you to your aims. If in doubt, consult the API docs of HttpClient.Builder.
Above we got acquainted with the promising API of the HTTP client. Let’s see some more examples of the HTTP client in action.
To save a downloaded file to the local file system, simple use HttpResponse.BodyHandler.asFile(Path)
:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://labs.consol.de/"))
.GET()
.build();
Path tempFile = Files.createTempFile("consol-labs-home", ".html");
HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandler.asFile(tempFile));
System.out.println(response.statusCode());
System.out.println(response.body());
Uploading a file from the local file system via POST is easy as well, we can add a POST body by using a HttpRequest.BodyProcessor
:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("http://localhost:8080/upload/"))
.POST(HttpRequest.BodyProcessor.fromFile(Paths.get("/tmp/file-to-upload.txt")))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.discard(null));
System.out.println(response.statusCode());
Asynchronous HTTP is as easy as using HttpClient#sendAsync()
instead of HttpClient#send
. If the server side supports HTTP/2, you can even cancel a running request:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://labs.consol.de/"))
.GET()
.build();
CompletableFuture<HttpResponse<String>> response = client.sendAsync(request, HttpResponse.BodyHandler.asString());
Thread.sleep(5000);
if(response.isDone()) {
System.out.println(response.get().statusCode());
System.out.println(response.get().body());
} else {
response.cancel(true);
System.out.println("Request took more than 5 seconds... cancelling.");
}
1 HttpClient client = HttpClient.newBuilder()
2 .proxy(ProxySelector.getDefault())
3 .build();
4
5 HttpRequest request = HttpRequest.newBuilder()
6 .uri(new URI("https://labs.consol.de"))
7 .GET()
8 .build();
9
10 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
11 System.out.println(response.statusCode());
12 System.out.println(response.body());
HttpClient client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password".toCharArray());
}
})
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://labs.consol.de"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());
The examples above show that we will be able to send HTTP requests easily with Java 9’s standard API. Furthermore, we will be able to process the responses in an elegant manner.
Of course there are third-party libraries who offer similar comfort, but it’s always good to have a decent out-of-the-box solution.