Let’s use the new HTTP Client that java 11 brought

Posted on by

Categories:     

100DaysOfProgramming_Day011

Java 11 brought an inbuilt HTTP client. As a result, we don’t need a third-party library anymore to invoke an HTTP request.

Let’s call a chuck Norris api to fetch a random joke.

package com.bazlur;

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

public class Day011 {
  public static final String CHUCK_NORRIS_RANDOM_JOKES_API = "https://api.chucknorris.io/jokes/random";

  public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
    var client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .connectTimeout(Duration.ofSeconds(20))
            .build();

    var request = HttpRequest.newBuilder()
            .uri(new URI(CHUCK_NORRIS_RANDOM_JOKES_API))
            .header("Accept", "application/json")
            .GET()
            .build();

    var response = client.send(request, HttpResponse.BodyHandlers.ofString());
    var body = response.body();
    System.out.println("body = " + body);
  }
}

We can even fetch it asynchronously; we just have to change the method send() to sendAsync()

 var completableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body);

It will return a CompletableFuture.

We can certainly go further to tie any serializer to serialize the JSON body into an object. However, Jackson is my favourite JSON library that I use in all my projects.

Let’s add its dependency:

implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.3'

I will use the sendAsync() method so that I can use the lambda chain. The readValue() of ObjectMapper’s method throws a checked exception that I don’t want to put into my lambda expression. It makes code ugly. Thus I will extend ObjectMapper’s behaviour a little to accommodate my need.

  static class UncheckedObjectMapper extends ObjectMapper {
    Joke readValue(String content) {
      try {
        return this.readValue(content, new TypeReference<>() {
        });
      } catch (JsonProcessingException e) {
        throw new CompletionException(e);
      }
    }
  }

I wouldn’t recommend this sort of the change in your production code unless you are willing to maintain it. So careful!

We usually get this JSON response from the api-

{
  "categories": [],
  "created_at": "2020-01-05 13:42:28.420821",
  "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
  "id": "Kqck_igkQNCw6Y0ADR2iyA",
  "updated_at": "2020-01-05 13:42:28.420821",
  "url": "https://api.chucknorris.io/jokes/Kqck_igkQNCw6Y0ADR2iyA",
  "value": "The Terminator wears a Chuck Norris tee shirt. ."
}

Let’s create a POJO for this. I will use record for this.


  @JsonIgnoreProperties(ignoreUnknown = true)
  record Joke(
          @JsonProperty("created_at")
          String createdAt,
          @JsonProperty("icon_url")
          String iconUrl,
          @JsonProperty("id")
          String id,
          @JsonProperty("updated_at")
          String updatedAt,
          @JsonProperty("url")
          String url,
          @JsonProperty("value")
          String value
  ) { }

Let’s put everything together.

package com.bazlur;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;

public class Day011_1 {

  public static final String CHUCKNORRIS_RANDOM_JOKES_API = "https://api.chucknorris.io/jokes/random";

  public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException, ExecutionException {

    var objectMapper = new UncheckedObjectMapper();
    objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);

    var httpClient = HttpClient.newHttpClient();
    var request = HttpRequest.newBuilder()
            .uri(new URI(CHUCKNORRIS_RANDOM_JOKES_API))
            .header("Accept", "application/json")
            .GET()
            .build();

    var joke = httpClient.sendAsync(request, BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenApply(objectMapper::readValue)
            .get();
    System.out.println("joke = " + joke.value());
  }


  @JsonIgnoreProperties(ignoreUnknown = true)
  record Joke(
          @JsonProperty("created_at")
          String createdAt,
          @JsonProperty("icon_url")
          String iconUrl,
          @JsonProperty("id")
          String id,
          @JsonProperty("updated_at")
          String updatedAt,
          @JsonProperty("url")
          String url,
          @JsonProperty("value")
          String value
  ) { }

  static class UncheckedObjectMapper extends ObjectMapper {
    Joke readValue(String content) {
      try {
        return this.readValue(content, new TypeReference<>() {
        });
      } catch (JsonProcessingException e) {
        throw new CompletionException(e);
      }
    }
  }
}

for copy/paste pleasure: https://github.com/rokon12/100DaysOfJava/blob/main/src/main/java/com/bazlur/Day011.java

           

Share on:

Author: A N M Bazlur Rahman

Java Champion | Software Engineer | JUG Leader | Book Author | InfoQ & Foojay.IO Editor | Jakarta EE Ambassadors| Helping Java Developers to improve their coding & collaboration skills so that they can meet great people & collaborate

100daysofcode 100daysofjava access advance-java agile algorithm arraylist article bangla-book becoming-expert biginteger book calculator checked checked-exceptions cloning code-readability code-review coding coding-convention collection-framework compact-strings completablefuture concatenation concurrency concurrentmodificationexception concurrentskiplistmap counting countingcollections critical-section daemon-thread data-race data-structure datetime day002 deliberate-practice deserialization design-pattern developers duration execute-around executors export fibonacci file file-copy fork/join-common-pool functional future-java-developers groupby hash-function hashmap history history-of-java how-java-performs-better how-java-works http-client image import inspiration io itext-pdf java java-10 java-11 java-17 java-8 java-9 java-developers java-performance java-programming java-thread java-thread-programming java11 java16 java8 lambda-expression learning learning-and-development linkedlist list local-type-inference localdatetime map methodology microservices nio non-blockingio null-pointer-exception object-cloning optional packaging parallel pass-by-reference pass-by-value pdf performance prime-number programming project-loom race-condition readable-code record refactoring review scheduler scrum serialization serversocket simple-calculator socket software-development softwarearchitecture softwareengineering sorting source-code stack string string-pool stringbuilder swing thread threads tutorial unchecked vector virtual-thread volatile why-java zoneid