Java 11 - Standard HTTP Client VS Apache HttpClient

In Java 11, the incubated HTTP APIs from Java 9 are now officially incorporated into the Java SE API (as stated in JEP 321). The API has seen a few changes, one of them is the API is now fully asynchronous. In this article, I will present two examples of REST API call using "Prior Java 11" way and using Java 11 APIs.

Once Upon a Time

In fact Java has had its own built-in HTTP client: HttpURLConnection since JDK1.1 (even the super-class, URLConnection is already exists since JDK1.0). But I believed most of us will use HttpComponents Client from Apache, which I also used in below example:

RESTCallHttpClient.java
package com.dariawan.jdk8;

import com.google.common.net.UrlEscapers;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

/**
 * REST Call using Apache HttpClient component
 */
public class RESTCallHttpClient {

    private static final String API_OFC_GET_UTC_DATE = "https://www.onlinefreeconverter.com/api/v1/getutcdate";
    private static final String API_BASE64CODE_ENCODE = "https://www.base64code.com/api/v1/base64/encode/";
    private static final String API_BASE64CODE_DECODE = "https://www.base64code.com/api/v1/base64/decode/";
    private static final String API_BASE64CODE_ENCODE_INPUT = "Dariawan | Java Blog and Tutorials";
    private static final String API_BASE64CODE_DECODE_INPUT = "RGFyaWF3YW4gfCBKYXZhIEJsb2cgYW5kIFR1dG9yaWFscw==";
    
    public static void main(String[] args) throws Exception {
        // set timeout
        int timeout = 10000; // 10s
        RequestConfig.Builder requestBuilder = RequestConfig.custom();
        requestBuilder = requestBuilder.setConnectTimeout(timeout);
        requestBuilder = requestBuilder.setConnectionRequestTimeout(timeout);        
      
        HttpClientBuilder builder = HttpClientBuilder.create();
        builder.setDefaultRequestConfig(requestBuilder.build());
        builder.setSSLSocketFactory(SSLUtil.getInsecureSSLConnectionSocketFactory());
        builder.setRedirectStrategy(new DefaultRedirectStrategy());
        HttpClient httpClient = builder.build();

        // Get UTC Date API
        HttpGet reqGetUtcDate = new HttpGet(API_OFC_GET_UTC_DATE);
        reqGetUtcDate.addHeader("Content-Type", "application/json");

        HttpResponse resGetUtcDate = httpClient.execute(reqGetUtcDate);
        int resGetUtcDateStatusCode = resGetUtcDate.getStatusLine().getStatusCode();
        System.out.println("<<< Apache HttpClient REST API Call: Get UTC Date >>>");
        if (resGetUtcDateStatusCode == 200 || resGetUtcDateStatusCode == 201) {
            HttpEntity httpEntity = resGetUtcDate.getEntity();
            String jsonOutput = EntityUtils.toString(httpEntity);
            System.out.println("Success!\n" + jsonOutput);
        } else {
            System.out.println("Failure! Status Code: " + resGetUtcDateStatusCode);
        }

        // Base64 Encode API
        String inputUrlEncoded = UrlEscapers.urlFragmentEscaper().escape(API_BASE64CODE_ENCODE_INPUT);
        HttpGet reqBase64Encode = new HttpGet(
                new StringBuilder(API_BASE64CODE_ENCODE)
                                .append(inputUrlEncoded)
                                .toString());
        reqBase64Encode.addHeader("Content-Type", "application/json");

        HttpResponse resBase64Encode = httpClient.execute(reqBase64Encode);
        int resBase64EncodeStatusCode = resBase64Encode.getStatusLine().getStatusCode();
        
        System.out.println("\n<<< Apache HttpClient REST API Call: Base64 Encode >>>");
        if (resBase64EncodeStatusCode == 200 || resBase64EncodeStatusCode == 201) {
            HttpEntity httpEntity = resBase64Encode.getEntity();
            String jsonOutput = EntityUtils.toString(httpEntity);
            System.out.println("Success!\n" + jsonOutput);
        } else {
            System.out.println("Failure! Status Code: " + resBase64EncodeStatusCode);
        }

        // Base64 Decode API
        HttpGet reqBase64Decode = new HttpGet(
                new StringBuilder(API_BASE64CODE_DECODE)
                                .append(API_BASE64CODE_DECODE_INPUT)
                                .toString());
        reqBase64Decode.addHeader("Content-Type", "application/json");

        HttpResponse resBase64Decode = httpClient.execute(reqBase64Decode);
        int resBase64DecodeStatusCode = resBase64Decode.getStatusLine().getStatusCode();

        System.out.println("\n<<< Apache HttpClient REST API Call: Base64 Decode >>>");
        if (resBase64DecodeStatusCode == 200 || resBase64DecodeStatusCode == 201) {
            HttpEntity httpEntity = resBase64Decode.getEntity();
            String jsonOutput = EntityUtils.toString(httpEntity);
            System.out.println("Success!\n" + jsonOutput);
        } else {
            System.out.println("Failure! Status Code: " + resBase64DecodeStatusCode);
        }
    }
    
    private static class SSLUtil {
        protected static SSLConnectionSocketFactory getInsecureSSLConnectionSocketFactory()
                throws KeyManagementException, NoSuchAlgorithmException {
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }

                        @Override
                        public void checkClientTrusted(
                                final java.security.cert.X509Certificate[] arg0, final String arg1)
                                throws CertificateException {
                            // do nothing and blindly accept the certificate
                        }

                        @Override
                        public void checkServerTrusted(
                                final java.security.cert.X509Certificate[] arg0, final String arg1)
                                throws CertificateException {
                            // do nothing and blindly accept the server
                        }
                    }
            };

            final SSLContext sslcontext = SSLContext.getInstance("SSL");
            sslcontext.init(null, trustAllCerts,
                    new java.security.SecureRandom());

            final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslcontext, new String[]{"TLSv1"}, null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            
            return sslsf;
        }
    }
}
                    

Let's dive a bit into RESTCallHttpClient code. I'm using three different webservices from OnlineFreeConverter (getUtcDate) and Base64Code (Encode and Decode). And using SSLUtil to handle SSL/certificate (I'll write about this in another post). All REST calls are GET, and when we run, we get this results:

<<< Apache HttpClient REST API Call: Get UTC Date >>> Success! {"utcdate": "2019-01-27", "day": 27, "month": 1, "year": 2019, "day_name": "Sunday", "month_name": "January", "day_of_the_year": 27, "week_of_year": 4} <<< Apache HttpClient REST API Call: Base64 Encode >>> Success! {"output": "RGFyaWF3YW4gfCBKYXZhIEJsb2cgYW5kIFR1dG9yaWFscw==", "message": "Success"} <<< Apache HttpClient REST API Call: Base64 Decode >>> Success! {"output": "Dariawan | Java Blog and Tutorials", "message": "Success"}

Java 11 API

Now, the real business start. Let's code in new API:

RESTCall.java
package com.dariawan.jdk11;

import com.google.common.net.UrlEscapers;
import java.net.URI;
import java.time.Duration;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

/**
 * REST Call using Java 11 APIs
 */
public class RESTCall {

    private static final String API_OFC_GET_UTC_DATE = "https://www.onlinefreeconverter.com/api/v1/getutcdate";
    private static final String API_BASE64CODE_ENCODE = "https://www.base64code.com/api/v1/base64/encode/";
    private static final String API_BASE64CODE_DECODE = "https://www.base64code.com/api/v1/base64/decode/";
    private static final String API_BASE64CODE_ENCODE_INPUT = "Dariawan | Java Blog and Tutorials";
    private static final String API_BASE64CODE_DECODE_INPUT = "RGFyaWF3YW4gfCBKYXZhIEJsb2cgYW5kIFR1dG9yaWFscw==";
    private static final HttpResponse.BodyHandler<String> AS_STRING = HttpResponse.BodyHandlers.ofString();
    private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2) // default
            .followRedirects(HttpClient.Redirect.NORMAL) // Always redirect, except from HTTPS URLs to HTTP URLs.
            .proxy(ProxySelector.getDefault())
            .build();

    public static void main(String[] args) throws Exception {

        // Get UTC Date API
        var reqGetUtcDate = HttpRequest.newBuilder()
                .uri(URI.create(API_OFC_GET_UTC_DATE))
                .timeout(Duration.ofSeconds(10))
                .header("Content-Type", "application/json")
                .build();

        var resGetUtcDate = HTTP_CLIENT.send(reqGetUtcDate, AS_STRING);
        var resGetUtcDateStatusCode = resGetUtcDate.statusCode();

        System.out.println("<<< Java 11 REST API Call: Get UTC Date >>>");
        if (resGetUtcDateStatusCode == 200 || resGetUtcDateStatusCode == 201) {
            System.out.println("Success!\n" + resGetUtcDate.body());
        } else {
            System.out.println("Failure! Status Code: " + resGetUtcDateStatusCode);
        }

        // Base64 Encode API
        var inputUrlEncoded = UrlEscapers.urlFragmentEscaper().escape(API_BASE64CODE_ENCODE_INPUT);
        var reqBase64Encode = HttpRequest.newBuilder()
                .uri(URI.create(//Set the appropriate endpoint
                        new StringBuilder(API_BASE64CODE_ENCODE)
                                .append(inputUrlEncoded)
                                .toString()))
                .timeout(Duration.ofSeconds(10))
                .header("Content-Type", "application/json")
                .build();

        var resBase64Encode = HTTP_CLIENT.send(reqBase64Encode, AS_STRING);
        var resBase64EncodeStatusCode = resBase64Encode.statusCode();

        System.out.println("\n<<< Java 11 REST API Call: Base64 Encode >>>");
        if (resBase64EncodeStatusCode == 200 || resBase64EncodeStatusCode == 201) {
            System.out.println("Success!\n" + resBase64Encode.body());
        } else {
            System.out.println("Failure! Status Code: " + resBase64EncodeStatusCode);
        }

        // Base64 Decode API
        var reqBase64Decode = HttpRequest.newBuilder()
                .uri(URI.create(//Set the appropriate endpoint
                        new StringBuilder(API_BASE64CODE_DECODE)
                                .append(API_BASE64CODE_DECODE_INPUT)
                                .toString()))
                .timeout(Duration.ofSeconds(10))
                .header("Content-Type", "application/json")
                .build();

        var resBase64Decode = HTTP_CLIENT.send(reqBase64Decode, AS_STRING);
        var resBase64DecodeStatusCode = resBase64Decode.statusCode();

        System.out.println("\n<<< Java 11 REST API Call: Base64 Decode >>>");
        if (resBase64DecodeStatusCode == 200 || resBase64DecodeStatusCode == 201) {
            System.out.println("Success!\n" + resBase64Decode.body());
        } else {
            System.out.println("Failure! Status Code: " + resBase64DecodeStatusCode);
        }
    }
}
                    

Will produce output:

<<< Java 11 REST API Call: Get UTC Date >>> Success! {"utcdate": "2019-01-27", "day": 27, "month": 1, "year": 2019, "day_name": "Sunday", "month_name": "January", "day_of_the_year": 27, "week_of_year": 4} <<< Java 11 REST API Call: Base64 Encode >>> Success! {"output": "RGFyaWF3YW4gfCBKYXZhIEJsb2cgYW5kIFR1dG9yaWFscw==", "message": "Success"} <<< Java 11 REST API Call: Base64 Decode >>> Success! {"output": "Dariawan | Java Blog and Tutorials", "message": "Success"}

My Take on Java 11 Standarized HTTP Client

Let's dive in. Using new API, I have no difficulties with SSL certificates which is a plus for me. I also like the ability to straightforward accessing response body (as String) which is more work to do using Apache HttpClient response (get from HttpEntity). Another plus point is method chaining

private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) // default .followRedirects(HttpClient.Redirect.NORMAL) // Always redirect, except from HTTPS URLs to HTTP URLs. .proxy(ProxySelector.getDefault()) .build();

Which allow us to construct objects as least configured as we would like them to be or as highly configured as we would like them to be. Minimum configuration like this:

private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder().build();

Also sufficient for the program to run correctly