Java 11 added a new HTTP client library. When we needed a HTTP client prior to Java 11, it was always Apache HTTP client libraries coming to the rescue. That was the maven libraries we will include without even thinking. Come Java 11, we have a new HTTP client library that can also handle HTTP/2 instead of just HTTP/1.1 supported earlier.
HTTP/2 gives lower latency and incorporates quite a few advanced features. This includes more intelligent packet streaming, header data compression and multiplexed queues over a single TCP connection. However, HTTP/2 stopped supporting chunked transfer encoding that was available in HTTP/1.1.
This new library supports both synchronous and asynchronous HTTP calls. Following the trends of Java, it uses reactive streams for request and response processing. I thought of using this library in one of my short POCs as I was building on Java 11. The convenience of not adding a separate dependency and ease of use was a relief.
Synchronous HTTP call
For the test we will just call a dummy rest service hosted on typicode.com. It will return us a dummy JSON response.
public void callSync(final String url) throws InterruptedException, IOException { final HttpClient client = HttpClient.newHttpClient(); final HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(Duration.ofMinutes(1)) .header("Accept", "application/json") .GET() .build(); final HttpResponse<?> response = client.send(request, BodyHandlers.ofString()); if (response.statusCode() == 200) { System.out.println(response.body()); } }
This utilizes the new version of HttpClient. We initialize an object of this on line 3. We utilize the builder pattern to setup all necessary parameters for this call. In this case what we setup is the URI, timeout for the call, method for the HTTP call and accept header. That is what is needed for a basic call. Next step we make a synchronous call on line 10. It expects a BodyHandler to be sent. This is also a new implementation that allows us to use different kind of handlers. In this case we are passing in to convert the body into into a String. However, we could have used a ByteArray, InputStream or File type of handlers to manage the response differently.
Passing in a request as below,
public static void main (final String [] args) throws Exception { final RestCalls call = new RestCalls(); call.callSync("https://jsonplaceholder.typicode.com/todos/1"); } /* //// RESPONSE //// { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false } */
Asynchronous HTTP call
New version of HttpClient also allows to call a service asynchronously. This makes it easier to call multiple services in one go and accumulate results on completion. Most of the intricacies is handled by the call itself. We change the sendSync call to sendAsync instead to make it asynchronous as shown below.
public void callAsync(final String url) throws InterruptedException, IOException, ExecutionException { final HttpClient client = HttpClient.newHttpClient(); final HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(Duration.ofMinutes(1)) .header("Accept", "application/json") .GET() .build(); final HttpResponse<?> response = client.sendAsync(request, BodyHandlers.ofString()) .get(); if (response.statusCode() == 200) { System.out.println(response.body()); } }
The only change we have in the code above is calling sendAsync. Also since this is a asynchronous call, we have to wait for the call to complete or provide a callback. I this case we have a get() on line 11 that will wait for the response to be completed.
Running the above code also gives the same response as synchronous version. Now let us put this in a real use case.
Use Case
We will consider a use case where we need to get an OAuth2 token and call a service. This is a very common use case that we have to deal every day while calling web services. In this scenario, we will first use the new web service to get an OAuth2 token from an Azure. For convenience we will assume that we have the client_id and client_sereat available. Microsoft document asks us to use the following format for token generation.
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 Host: https://login.microsoftonline.com Content-Type: application/x-www-form-urlencoded client_id=6731de76-14a6-49ae-97bc-6eba6914391e &scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read &code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr... &redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F &grant_type=authorization_code &code_verifier=ThisIsntRandomButItNeedsToBe43CharactersLong &client_secret=JqQX2PNo9bpM0uEihUPzyrh // NOTE: Only required for web apps. This secret needs to be URL-Encoded.
Further details for what each of these parameters means can be read from the Microsoft website. What is significant here is that it is a post call with parameters sent as URL encoded form parameters. After we get the token we will call a web service using the OAuth token.
Token generation
The token returned by Microsoft is JSON. For the convenience of parsing it, we will use a small JSON parser.
<dependencies> <dependency> <groupId>com.googlecode.json-simple</groupId> <artifactId>json-simple</artifactId> <version>1.1.1</version> </dependency> </dependencies>
Next we make a call to Azure Service to get the token as follows.
public String getAuthToken(final String url) throws InterruptedException, IOException, ParseException { String token = null; final Map<String, String> params = new HashMap<>(); params.put("grant_type", "client_credentials"); params.put("client_id", "{client_id}"); params.put("client_secret", "{client_secret}"); final String form = params.keySet().stream() .map(key -> key + "=" + URLEncoder.encode(params.get(key), StandardCharsets.UTF_8)) .collect(Collectors.joining("&")); final HttpClient client = HttpClient.newHttpClient(); final HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(Duration.ofMinutes(1)) .header("Accept", "application/json") .header("Content-Type", "application/x-www-form-urlencoded") .POST(BodyPublishers.ofString(form)) .build(); final HttpResponse<?> response = client.send(request, BodyHandlers.ofString()); if (response.statusCode() == 200) { final JSONParser jsonParser = new JSONParser(); final JSONObject obj = (JSONObject)jsonParser.parse(response.body().toString()); token = obj.get("access_token").toString(); } return token; }
We create the form parameters all the way to line 12. Then we construct appropriate HttpClient request and setup correct headers. One more thing of interest here is the BodyPublishers. Similar to BodyHandlers, it allows us to create the POST body using different java types. We are creating the body using the form string we built above. Finally if we see that the call succeeded (HTTP response code 200), we extract the token from ‘access_token’ field in the JSON object.
Now that we have a token, we can easily call the web service. Web Service signature will be based on how it was created. For convenience we will assume a simple service that can be invoked using GET and needs an OAuth2 bearer token to sent.
Call Microservice
This is the easy part of the chain. Since we already know what the service needs, we will just call appropriately.
public void callAzureFunction(final String url) throws InterruptedException, IOException, ParseException { final AuthToken tkn = new AuthToken(); final String token = tkn.getAuthToken(AUTH_URL); final HttpClient client = HttpClient.newHttpClient(); final HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(Duration.ofMinutes(1)) .header("Accept", "application/json") .header("Authorization", "Bearer " + token) .GET() .build(); final HttpResponse<?> response = client.send(request, BodyHandlers.ofString()); System.out.println(response.body()); }
This is a sample function call deployed on Azure. We are sending the Bearer OAuth2 token (line 13) to call this web service. Finally we just print the response out.
Conclusion
In this post, we took a look at the new HttpClient introduced in Java 11. Finally we do not have to include the Apache Http library anymore to use the powerful features. Hope you found the post useful. Ciao for now!