New HTTP Client in JDK 11

This article explores the new HTTP client [1] incubated [2] in JDK 9 and finalized in JDK 11.

1. Pre-requisites

  1. Install JDK 11 early-access build from here.

    I used OpenJDK 11 build for Windows for this exercise.
  2. Install IntelliJ IDEA version 2018.1+ from here.

    I used IntelliJ IDEA 2018.1 Community Edition for this exercise.
  3. (Optional) Create a Kotlin module in the IDE.
    I used Kotlin programming language for this exercise (it is awesome!). However, Kotlin is not mandatory for this exercise. You can use Java programming language.

2. Simple GET request

We will send a GET request to httpbin.org/uuid.

import java.net.URI
import java.net.http.HttpClient (1)
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration

fun main(args: Array<String>) {

    val request =
        HttpRequest
            .newBuilder()
            .GET() (2)
            .uri(URI.create("http://httpbin.org/uuid")) (3)
            .timeout(Duration.ofSeconds(20)) (4)
            .version(HttpClient.Version.HTTP_1_1) (5)
            .build() (6)

    val response =
        HttpClient
            .newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString())

    val statusCode = response.statusCode()
    val responseHeaders = response.headers().map()
    val responseBody = response.body()

    printResponse(statusCode, responseHeaders, responseBody)
}
1 HTTP Client API.
2 Request method is GET.
3 URI is httpbin.org/uuid.
4 The request will timeout after 20 seconds.
5 HTTP version is HTTP/1.1. Other choice is HTTP/2.
6 Create an object of type java.net.http.HttpRequest.
The code of printResponse method has been omitted for brevity.

The above function prints the following:

Response Status Code: 200

Response Headers: {
  access-control-allow-credentials=[true]
  access-control-allow-origin=[*]
  connection=[keep-alive]
  content-length=[53]
  content-type=[application/json]
  date=[Sun, 22 Apr 2018 17:55:15 GMT]
  server=[meinheld/0.6.1]
  via=[1.1 vegur]
  x-powered-by=[Flask]
  x-processed-time=[0]
}

Response Body: {
  "uuid": "c172a2a0-0848-4a7b-b3e8-39624a0a1f4a"
}

3. Basic Authentication

The HTTP client can do HTTP Basic Authentication [3].

import java.net.Authenticator
import java.net.PasswordAuthentication
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

fun main(args : Array<String>) {

    val username = "john.doe"
    val password = "johns-password"

    val request =
        HttpRequest
            .newBuilder()
            .GET()
            .uri(URI.create("http://httpbin.org/basic-auth/$username/$password")) (1)
            .build()

    val response =
        HttpClient
            .newBuilder()
            .authenticator(createAuthenticator(username, password.toCharArray())) (2)
            .build()
            .send(request, HttpResponse.BodyHandlers.ofString())

    if (response.statusCode() != 200) {
        throw RuntimeException("Expected status code '200', received '${response.statusCode()}'")
    }

    println(response.body())
}

private fun createAuthenticator(username : String, password : CharArray) = object : Authenticator() {

    override fun getPasswordAuthentication(): PasswordAuthentication {
        return PasswordAuthentication(username, password)
    }
}
1 httpbin.org/basic-auth/foo/bar will return success code only if the request includes HTTP Basic Authentication header with foo as the username and bar as the password.
2 Use java.net.PasswordAuthentication authenticator to add HTTP Basic Authentication header to the request.

The above function prints the following:

{
  "authenticated": true,
  "user": "john.doe"
}

4. HTTP/2

The HTTP client can be requested to use a preferred HTTP version - HTTP/1.1 or HTTP/2. The actual HTTP version used depends on the HTTP versions supported by the server.

The following program prints the actual HTTP version used for various preferred HTTP versions and websites supporting various HTTP versions.

package rahulb.experiments.jdk.http.client

import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration

fun main(args: Array<String>) {

    val uris =
        listOf(
            "http://httpbin.org/uuid",
            "http://nghttp2.org/httpbin/get"
        )

    val preferredVersions =
        listOf(
            null,
            HttpClient.Version.HTTP_1_1,
            HttpClient.Version.HTTP_2
        )

    val pairs = uris.flatMap { uri -> preferredVersions.map { preferredVersion -> Pair(uri, preferredVersion) } }

    val outcomes = pairs.map { (uri, preferredVersion) -> getOutcome(uri, preferredVersion) }

    printOutcomes(outcomes)
}

private fun getOutcome(uri: String, preferredVersion: HttpClient.Version?): Triple<String, HttpClient.Version?, HttpClient.Version> {

    val request =
        HttpRequest
            .newBuilder()
            .GET()
            .uri(URI.create(uri))
            .timeout(Duration.ofSeconds(20))
            .apply { if (preferredVersion != null) version(preferredVersion) }
            .build()

    val response =
        HttpClient
            .newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString())

    if (response.statusCode() != 200) {
        throw RuntimeException("Error: (${response.statusCode()}) ${response.body()}")
    }

    val actualVersion = response.version()

    return Triple(uri, preferredVersion, actualVersion)
}

The observations from the output of the program are as follows:

URI Highest HTTP version supported by the server Client’s preferred HTTP version Actual HTTP version used

httpbin.org/uuid

HTTP/1.1

null

HTTP/1.1

httpbin.org/uuid

HTTP/1.1

HTTP/1.1

HTTP/1.1

httpbin.org/uuid

HTTP/1.1

HTTP/2

HTTP/1.1

nghttp2.org/httpbin/get

HTTP/2

null

HTTP/2

nghttp2.org/httpbin/get

HTTP/2

HTTP/1.1

HTTP/1.1

nghttp2.org/httpbin/get

HTTP/2

HTTP/2

HTTP/2

The conclusions from those observations are as follows:

  1. When a preferred HTTP version is not specified, the actual version is equal to the highest version supported by the server.

  2. If the preferred HTTP version is higher than the highest version supported by the server, the actual version is the highest version supported by the server.

  3. The actual HTTP version is equal to the preferred version if the server supports it, even if the server supports a higher version too.