Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
143: Building a Basic HTTP Client with HttpClient
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
HttpClientand anHttpRequesttargetinghttps://api.github.com/users/{username}(replace{username}with any valid GitHub handle, such as "google" or "microsoft"). - The GitHub API requires a
User-Agentheader; 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
sendAsyncto 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."
There are no comments for now.