The HttpClient in Java 11

The new and fresh HttpClient is long overdue. Finally support for the latest HTTP/2 and Web Socket. But what if you want to cache the responses you receive? And how do you add custom behaviour as there is no source code available?

Decorator Design Pattern

This is a nice application of the Decorator Design Pattern. You can visualize this pattern as an onion. In this particular case, the onion core is Java’s HttpClient, and the next layer is the caching we’ll add.

Let’s code!

The steps we will implement are the following:

  1. Add a new implementation of HttpClient, by creating a new subclass CachingHttpClient;
  2. This implementation will have
    • a HttpClient field to delegate the requests to;
    • a basic Map<HttpRequest, HttpResponse> field for caching. I use a Map to keep things simple, feel free to add a real cache provided by Guava or its successor Caffeine;
    • all abstract methods will call the same method on the delegate field, except the send method.
public class CachingHttpClient extends HttpClient{
  private Map<HttpRequest, HttpResponse> cache;
  private HttpClient delegate;
  public CachingHttpClient(HttpClient client){
      cache = new HashMap<>();
      delegate = client;
  }

  // example how to delegate a method we don't want to enhance
  public SSLContext sslContext(){
      return delegate.sslContext();
  }

  // add caching to the send method
  public <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException{
      // check if request is in cache, if yes the return cached response
      if(cache.containsKey(request)){
          System.out.println("Cache hit!");
          return cache.get(request);
      }
      // request is not in cache, let's execute it with the delegate
      HttpResponse<T> response = delegate.send(request, responseBodyHandler);
      //add response to cache
      cache.put(request, response);
      //return response
      return response;
  }
}

Testrun

Let’s do two calls to the /users endpoint of jsonplaceholder.com . The first time, the request is not in the cache and should execute the request. The second time it should use the cached response.

public static void main(String[] args) throws IOException, InterruptedException {
    // create a default HttpClient
    HttpClient client = HttpClient.newHttpClient();
    // create an instance of our cachinghttpclient
    CachingHttpClient cachingHttpClient = new CachingHttpClient(client);
    //create the request for the users endpoint
    HttpRequest request = HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/users"))
            .GET().build();
    System.out.println("doing first call");
    cachingHttpClient.send(request, HttpResponse.BodyHandlers.ofString());
    System.out.println("doing second call");
    cachingHttpClient.send(request, HttpResponse.BodyHandlers.ofString());
}

Output of this simple program:

doing first call
doing second call
Cache hit!