How to Build a HttpClient in Java 11+

Updated dimitrilc 3 Tallied Votes 465 Views Share

Introduction

Based on the 2020 Java Ecosystem survey from Jetbrains, 75% of Java developers are still using JDK 8. JDK 11 introduces HttpClient, which replaces the legacy HttpURLConnection API. For this tutorial, we will build a HttpClient that consumes the free Cat Facts API.

Goals

At the end of this tutorial, you would have learned:

  1. How to use the HttpClient to consume REST APIs.

Prerequisite Knowledge

  1. Basic Java.
  2. Good understanding of HTTP protocols.

Tools Required

  1. Any Java IDE that supports JDK 11 and above.

The Application Entry Point

Our App does not need any special build tool. Follow the steps below to create the main method:

  1. Create an empty Java project with support for JDK 11+.
  2. Create a package called com.example.client.
  3. Inside the client package, create a class called Entry.
  4. Add the main method inside Entry.

Your Entry.java file should now look like this:

package com.example.client;

public class Entry {

   public static void main(String[] args){
   }
}

The CatFactsService Class

Next, we need to create a class for grouping all of the logics for our HttpClient.

  1. Inside the same Entry.java file, create another top level class called CatFactsService with package visibility (empty modifier).
  2. Add a private final instance variable with type HttpClient(from java.net.http) and identifier httpClient.
  3. Add an empty constructor and initialize httpClient using the static method newHttpClient() from the HttpClient class. This will initialize a HttpClient with default settings, which is sufficient for this tutorial. If you want to obtain an instance of HttpClient with custom settings, you can use the static method newBuilder() from HttpClient instead.

The CatFactsService class should now look like this:

class CatFactsService {
   private final HttpClient httpClient;

   CatFactsService() {
       this.httpClient = HttpClient.newHttpClient();
   }
}

Preparing for Requests

There are a couple of steps that we will have to follow before sending a simple request:

  1. Build the request: setting headers, parameters, body content, uri, etc. We will use HttpRequest.Builder for this.
  2. Decides whether we want an async or synchronous request, so we can handle the response properly.
    a. If the request is synchronous, we would use the send() method and assign the response to HttpResponse.
    b. If the request is async, we would use the sendAsync() method and assign the response to CompletableFuture.
  3. Whether we want to use a default HttpResponse.BodyHandler implementation or a custom implementation.

Retrieving a Random Cat Fact

The first request that we will build is a synchronous GET request.
Add a private static final String randomFactURI variable into CatFactsService. Initialize it with “https://catfact.ninja/fact?max_length=%d”. This variable serves as a constant for other methods in the class. Hard-coded URIs can change and is not a good practice, so loading String constants from an external source such as the .properties file is preferred. I am only using hard-coded String objects to keep the tutorial simple to understand.

private static final String randomFactURI = "https://catfact.ninja/fact?max_length=%d";

Add the getRandomCatFact method to the CatFactsService.

public String getRandomCatFact(int maxLength) throws IOException, InterruptedException {
   //Creates a new request for sending
   HttpRequest req = HttpRequest.newBuilder()
           .uri(URI.create(String.format(randomFactURI, maxLength)))
           .build();

   //Gets response with the body as String
   HttpResponse<String> res = this.httpClient.send(req, HttpResponse.BodyHandlers.ofString());

   return res.body();

}

Here are the explanation for the method above:

  1. The cat fact API has an endpoint to get a random cat fact, and the only customization we can make is the max length of the cat fact String. The max length is set as a request parameter. Our method takes a parameter of int, which is the max length that callers can set.
  2. In the String constant that you added in the previous step, there is a placeholder %d String. Based on Java Formatter rules, this allows us to insert an integral type into the placeholder String. The int parameter would replace %d.
  3. The first statement that declares req and initializes an HttpRequest object is where we build our request. Since we only need to modify the URI parameters, we would call the uri() method to set our custom URI.
  4. The second statement is where we send a synchronous request and assigns the response to the HttpResponse object.
  5. Because the API returns the cat fact as a json object in the body of the response, we need to return the body of the response to our caller.

Use the CatFactsService

In the main method, we need to initialize a CatFactsService instance, and then use the getRandomCatFact method to get random cat fact. Here is the content of the main method:

var service = new CatFactsService();

String jsonCatFact1 = service.getRandomCatFact(70);
String jsonCatFact2 = service.getRandomCatFact(140);

System.out.println(jsonCatFact1);
System.out.println(jsonCatFact2);

After running main, you will receive two json objects (as String). Here are what I received:

{"fact":"Neutering a cat extends its life span by two or three years.","length":60}
{"fact":"Many cats love having their forehead gently stroked.","length":52}

Solution Code

package com.example.client;

import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.URI;
import java.net.http.HttpResponse;

public class Entry {

   public static void main(String[] args) throws IOException, InterruptedException {
       var service = new CatFactsService();

       String jsonCatFact1 = service.getRandomCatFact(70);
       String jsonCatFact2 = service.getRandomCatFact(140);

       System.out.println(jsonCatFact1);
       System.out.println(jsonCatFact2);
   }
}

class CatFactsService {
   private final HttpClient httpClient;

   private static final String randomFactURI = "https://catfact.ninja/fact?max_length=%d";

   CatFactsService() {
       this.httpClient = HttpClient.newHttpClient();
   }

   public String getRandomCatFact(int maxLength) throws IOException, InterruptedException {
       //Creates a new request for sending
       HttpRequest req = HttpRequest.newBuilder()
               .uri(URI.create(String.format(randomFactURI, maxLength)))
               .build();

       //Gets response with the body as String
       HttpResponse<String> res = this.httpClient.send(req, HttpResponse.BodyHandlers.ofString());

       return res.body();
   }

}

Summary

The project that we created is a good starting point if you want to create an HTTP client to consume REST APIs. As for why HttpClient was introduced in JDK 11, you can check out JEP 110 and JEP 321.

The project source code can be found here: https://github.com/dmitrilc/DaniWebJavaHttpClient

JamesCherrill commented: Why the "this" on line 38? +15
dimitrilc 30 Junior Poster

Hi James, while the this keyword is unnecessary on line 38 and the constructor, I find it makes the code easier to read, especially outside of an IDE, like a blog post, for example.

The other Java devs I talked to or have reviewed my code didn't seem to mind or even realize that it's there. When I searched online, it looks like a 60/40 split (I'm guessing ~60% of devs prefer skipping the implicit whenever possible). I also don't see anything against it in either the Google Java Style/Oracle Java Style guides.

Neither Sonar Lint or IntelliJ gave me any warning at all (although IntelliJ has an optional inspector to remove unnecessary this).

So I guess it's a matter of preference.

JamesCherrill 4,733 Most Valuable Poster Team Colleague Featured Poster

Hi Dimitrilc

OK, thanks for the reply. That's what I thought, but I just wanted to check that I hadn't missed something.

It is 100% valid Java, even if it's redundant. My preference is in the 60% who would skip it, so when I saw it there I immediatley started to look for a variable shadowing situation, while also wondering why that situation would have been created here.

The jetbrains survey is interesting. I seem to remember another of their surveys thats showed that jetbrains was massively more popular than Eclipse or Netbeans, which seemed suspicious. Maybe it over-represents jetbrains users?

JDK 8 is out of support. JDK11 is the current LTS, so moving to the new goodies (like HttpClient) makes good sense. Thanks for taking the time to prepare and post this. How about some more? :)
J.

dimitrilc 30 Junior Poster

I believe IntelliJ has 60-80% market share, then Eclipse, with VSC and Netbeans fighting for 3rd place. VSC is still missing basic things like Javadocs preview.

IntelliJ is massively popular with the younger devs because they get the ultimate version for free with an edu email. The younger devs I talk to are mostly discord devs and minecraft modders.

JamesCherrill 4,733 Most Valuable Poster Team Colleague Featured Poster

Interesting.
I suspect that IDE choice varies a lot by market segment. To be honest I have zero knowledge of the preferences of younger minecraft modders, but in the major corporates where I did all my consulting Eclipse was dominant. IBM's involvement may have been a major factor; Eclipse's strengths for complex multi-product builds was another.

Now I live a more quiet life and am settled with NetBeans on MacOS. I switched from Eclipse when Java 8 was coming and Eclipse had no pre-release support for it but NetBeans allowed me to get into the upcoming goodies like lambdas and streams. NB with Zulu's ARM JDK 16 on Apple's new M1 silicon is just ridiculously fast.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.