Skip to Content
Course content

143: Building a Basic HTTP Client with HttpClient

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR from a junior developer who was trying to integrate a simple currency exchange API into our backend. When I opened the file, I found a sprawling mess of HttpURLConnection code. He had written nearly fifty lines of boilerplate just to open a connection, check the response code, and manually loop through an InputStream to build a StringBuilder. It was brittle, hard to read, and honestly, a nightmare to maintain. I remember telling him, "Stop. There is a much better way to do this."

Since Java 11, we've had the java.net.http.HttpClient. It replaced the clunky, legacy ways of handling HTTP requests with a modern, fluent API that feels more like how we actually think about the web: you create a client, you define a request, and you handle the response.

Orchestrating the Client and Request

To get started, you need to understand that the process is split into three distinct objects. First, there is the HttpClient itself, which acts as the engine. You can configure it with things like timeouts or a cookie handler, but for most basic tasks, the default builder works perfectly. Then, you have the HttpRequest, where you define the target URI, the method (GET, POST, etc.), and the headers.

Take a look at this example. Instead of a "Hello World," let's imagine we're pulling a dummy post from a placeholder API to populate a blog feed:

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
    .header("Accept", "application/json")
    .GET()
    .build();

I like this approach because it's declarative. You aren't managing sockets or streams manually; you're simply describing what you want the request to look like. If you needed to send data—say, for a POST request—you'd just add a .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) call to that chain.

Capturing and Processing the Response

Once the request is built, you send it using the client. The magic happens with the HttpResponse.BodyHandlers. In the old days, you had to manually read the bytes from the stream. Now, you just tell Java how you want the response body treated. Usually, you want it as a string.

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() == 200) {
    System.out.println("Success: " + response.body());
} else {
    System.out.println("Request failed with code: " + response.statusCode());
}

One thing to keep in mind: client.send() is a synchronous call. It blocks the current thread until the server responds. In a simple CLI tool, that's fine. But if you're building a UI or a high-throughput server, blocking your main thread is a recipe for a frozen application.

Handling Requests Asynchronously

This is where HttpClient really shines compared to the legacy tools. Instead of send(), you can use sendAsync(). This returns a CompletableFuture, allowing your program to keep running while the network request happens in the background. You can then chain actions to happen once the data finally arrives.

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println)
    .join(); // join() is used here just to prevent the JVM from exiting before the response arrives

I usually recommend sendAsync for any production-grade application. It prevents the "hanging" feeling users get when a network call takes a few seconds longer than expected. Just be careful with your exception handling; since it's asynchronous, you'll want to use .exceptionally() to catch network timeouts or DNS failures without crashing your entire thread pool.




📋 Practical Task

Build a GitHub Public Profile Fetcher

Your task is to create a small program that fetches the public profile data for a specific GitHub user. You will need to use the GitHub REST API, which is open for public profiles.

Requirements:

  • Create an HttpClient and an HttpRequest targeting https://api.github.com/users/{username} (replace {username} with any valid GitHub handle, such as "google" or "microsoft").
  • The GitHub API requires a User-Agent header; if you don't provide one, the API will return a 403 Forbidden error. Add a header like .header("User-Agent", "Java-HttpClient-Lesson").
  • Use sendAsync to fetch the data so the request is non-blocking.
  • Print the raw JSON response body to the console only if the status code is 200. If it is 404, print "User not found."
Rating
0 0

There are no comments for now.

to be the first to leave a comment.