16.2. Java’s java.net.http Library

Java (versions >=11) ships with java.net.http, a package that provides classes and interfaces that understand HTTP. It enables Java programs to access web content without worrying about the low-level details of the data exchange we described earlier. In this reading, we are going to focus on these three types:

Class

Description

HttpRequest

Represents an HTTP request.

HttpClient

Used to send HTTP requests and receive their HTTP responses.

HttpResponse

Represents an HTTP response.

Some complete code examples that involve all three types are included near the end of the chapter.

16.2.1. HttpRequest

The HttpRequest class provided by java.net.http allows Java programs to “build” HTTP request messages that can be sent using an HTTP client. Instead of providing a public constructor, the authors of HttpRequest decided to use the builder pattern — HttpRequest objects are constructed by building them using an HttpRequest.Builder object returned from HttpRequest.newBuilder(). The authors’ use of the builder pattern prevents the construction of incomplete request objects and provides a nice alternative to potentially complex looking constructor calls.

Most of the methods provided by HttpRequest.Builder merely update the request information stored in the builder object, then just return a reference to builder object itself so that you can update it further via additional method calls. Once all the request information is specified, the build() method is called to construct the actual HttpRequest object. The following two code snippets build a request object using the exact same statement — the second snippet distributes the statement across several lines to make it easier to read:

HttpRequest request = HttpRequest.newBuilder().uri(location).build();
HttpRequest request = HttpRequest.newBuilder()
    .uri(location) // sets the HttpRequest's URI
    .build();      // builds and returns an HttpRequest

The HTTP specification requires that requests indicate their purpose and expectations regarding a successful result by setting a request method value. By default, HttpRequest.Builder builds “GET” requests that ask an HTTP server to include the requested content in the body of the response message that is sent back to the client. The following two code snippets build the same “GET” request:

HttpRequest request = HttpRequest.newBuilder()
    .uri(location) // sets the HttpRequest's URI
    .GET()         // sets the HttpRequest's request method to GET
    .build();      // builds and returns an HttpRequest
HttpRequest request = HttpRequest.newBuilder()
    .uri(location) // sets the HttpRequest's URI
    .build();      // builds and returns an HttpRequest

Other request method values are outside the scope of this reading; however, readers who are interested should note that HttpRequest.Builder does include Java methods to specify a different request method value if “GET” is not what you need.

Here is an example that builds an HttpRequest for an image:

URI location = URI.create("http://csweb.cs.uga.edu/~mec/cs1302/gui/pikachu.png");
HttpRequest request = HttpRequest.newBuilder()
    .uri(location) // sets this HttpRequest's request URI
    .build();      // builds and returns an HttpRequest.

Note

The classes and interfaces in java.net.http use the URI class to represent location / address information for web content. You are likely familiar with the concept of a URL; all URLs are also URIs.

Note

The cs1302.web/cs1302.web.Example1 program provided in this chapter’s examples demonstrates how to build a request for an image and create a JavaFX Image object using the data included in the body of the associated response.

Some HTTP servers host Application Programming Interfaces (APIs) that we can interact with using HTTP requests — instead of a URI referring to a “page” or “file”, it refers to structured “data” that our program might leverage to accomplish some goal. For example, the GitHub REST API provides URIs for accessing information stored by GitHub. Since GitHub supports many open source projects, their API provides a URI for structured data about open source software licenses. The example below builds an HttpRequest to get a license (in this case, the MIT license) using the GitHub REST API. According to GitHub’s API documentation, they recommend setting the “Accept” header when building a request — headers are one way to provide an HTTP server with more information about a request. Here is the code:

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.github.com/licenses/MIT"))
    .header("Accept", "application/vnd.github.v3.text-match+json")
    .build();

Note

The cs1302.web/cs1302.web.Example2 program provided in this chapter’s examples demonstrates how to build a request for license data and use the Google Gson library to parse the JSON-formatted string included in the body of the associated response.

Some HTTP servers also let you specify request metadata using a special query string included near the end of the request URI. Special care must be taken when including a query string in a URI so that the metadata values are encoded properly using a combination of URLEncoder.encode and StandardCharsets.UTF_8. The example below builds an HttpRequest that queries the iTunes Search API for up to 5 records related to “Daft Punk”.

String term = URLEncoder.encode("daft punk", StandardCharsets.UTF_8); // "daft+punk"
String limit = URLEncoder.encode("5", StandardCharsets.UTF_8);        // "5"
String query = String.format("?term=%s&limit=%s", term, limit);       // "?term=daft+punk&limit=5"

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://itunes.apple.com/search" + query))
    .build();

Note

The cs1302.web/cs1302.web.Example3 program provided in this chapter’s examples demonstrates how to build a request for the iTunes Search API and use the Google Gson library to parse the JSON-formatted string included in the body of the associated response.

16.2.2. HttpClient

The HttpClient class provided by java.net.http includes a send method to send an HTTP request message (described by an HttpRequest object) and return the corresponding HTTP response message (described as an HttpResponse<T> object). The HttpClient class also uses the builder pattern for object creation.

Here is a quick example that builds an HttpClient with preferred, modern settings:

HttpClient httpClient = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)           // uses HTTP protocol version 2 where possible
    .followRedirects(HttpClient.Redirect.NORMAL)  // always redirects, except from HTTPS to HTTP
    .build();                                     // builds and returns an HttpClient

Since a single HttpClient object can be used to send multiple requests, you are encouraged to only create one HttpClient object for your program, unless a specific need to do otherwise arises — you might do this by defining a static constant:

public static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)           // uses HTTP protocol version 2 where possible
    .followRedirects(HttpClient.Redirect.NORMAL)  // always redirects, except from HTTPS to HTTP
    .build();                                     // builds and returns an HttpClient

Once built, an HttpClient object’s send method can be called to send an HttpRequest; when doing so, an HttpResponse.BodyHandler<T> must also be supplied so that the HttpClient object knows how to construct the HttpResponse<T> object it creates for the response message. The HttpResponse.BodyHandlers class provides some static methods to create create commonly used HttpResponse.BodyHandler<T> objects:

Method

Response Type

Response Body Type

BodyHandlers.ofString()

Response<String>

String

BodyHandlers.ofInputStream()

Response<InputStream>

InputStream

In the example below, we access a copy of The Adventures of Sherlock Holmes by Arthur Conan Doyle that is hosted by Project Gutenberg — when we send the request, we use BodyHandlers.ofString() to inform the client that we want it to interpret the body of the response (i.e., the response content) as a string.

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.gutenberg.org/files/1661/1661-0.txt"))
    .build();
HttpResponse<String> response = HTTP_CLIENT.send(request, BodyHandlers.ofString());
String body = response.body();

Note

The cs1302.web/cs1302.web.Example0 program provided in this chapter’s examples is a rewritten version of the code above so that you can see it alongside the required exception handling.

16.2.3. HttpResponse<T>

If an HTTP response message is received, then the program must decide what to do based on the information contained in that response message. This information can be accessed by calling methods on the associated HttpResponse object. Here are some typical examples:

HttpResponse<T> Method

Details & Notes

statusCode()

The HTTP “status code” integer indicates whether the response should be considered successful or not. A value of 200 means “OK,” and is typically what you want. Other values either indicate problems (e.g., 404 means the remote server was unable to find what we requested) or that further steps need to be taken (e.e., 301 means “Permanently Moved” the requested content has moved to some new location). In this course, we will consider any status code that is not 200 to be problematic. A list that describes more HTTP status codes can be found here.

body()

The body of an HTTP request is the content of the response. The return type of body() is is determined by the <T> in HttpResponse<T> and processed internally using the HttpResponse.BodyHandler<T> that was used to send the request. It’s possible for the body of an HTTP response with a problematic status code to still include content; however, in this course, we will ignore the response body in such cases.

Here is a generic method that you can use to throw an exception if the status code of a supplied response is not 200 (OK) — you can see it used in several of the code examples provided in this chapter’s examples section:

/**
 * Throw an {@link java.io.IOException} if the HTTP status code of the
 * {@link java.net.http.HttpResponse} supplied by {@code response} is
 * not {@code 200 OK}.
 * @param <T> response body type
 * @param response response to check
 * @see <a href="https://httpwg.org/specs/rfc7231.html#status.200">[RFC7232] 200 OK</a>
 */
private static <T> void ensureGoodResponse(HttpResponse<T> response) throws IOException {
    if (response.statusCode() != 200) {
        throw new IOException(response.toString());
    } // if
} // ensureGoodResponse