Introduction
Yesterday a team-member asked me what I think is the best way to call two REST APIs in parallel with the additional requirement that the final output is computed based on the result obtained by both.
The optimum way to achive the above would be to make two parallel calls to the APIs, wait until both calls returns and then use the results to prepare the final output. For example if one of the API takes 1 second and the other takes 0.5 seconds, calling both will result in both results being available after 1 second. The next question is how do we do this?
There are multiple ways the above code can be written. This blog post discusses three approaches, one of which requires Java 11.
- Using Apache HttpClient with an executor
- Using Apache Async HttpClient
- Using Java 11's HttpClient
Using Apache HttpClient with an executor
private static void requestWithApacheHttpClient(String url1, String url2) throws IOException { CountDownLatch latch = new CountDownLatch(2); AtomicInteger n1 = new AtomicInteger(); AtomicInteger n2 = new AtomicInteger(); m_executor.execute(()-> { try { HttpGet getReq = new HttpGet(url1); CloseableHttpResponse resp = m_closableHttpClient.execute(getReq); InputStream is = resp.getEntity().getContent(); String result = EntityUtils.toString(resp.getEntity()); n1.set(Integer.parseInt(result)); latch.countDown(); } catch(Exception e) { e.printStackTrace(); } }); m_executor.execute(()-> { try { HttpGet getReq = new HttpGet(url2); CloseableHttpResponse resp = m_closableHttpClient.execute(getReq); InputStream is = resp.getEntity().getContent(); String result = EntityUtils.toString(resp.getEntity()); n2.set(Integer.parseInt(result)); latch.countDown(); } catch(Exception e) { e.printStackTrace(); } }); try { latch.await(); int n = n1.get() + n2.get(); System.out.printf("\nCurrentMS: %d, result: %d", System.currentTimeMillis(), n); } catch(Exception e) { e.printStackTrace(); } }
Using Apache Async HttpClient
private static void requestWithApacheAsyncHttpClient(String url1, String url2) throws Exception { Futuref1 = m_apacheAsyncHttpClient.execute(new HttpGet(url1), null); Future f2 = m_apacheAsyncHttpClient.execute(new HttpGet(url2), null); String r1 = EntityUtils.toString(f1.get().getEntity()); int n1 = Integer.parseInt(r1); String r2 = EntityUtils.toString(f2.get().getEntity()); int n2 = Integer.parseInt(r2); int n = n1 + n2; System.out.printf("\nCurrentMS: %d, result: %d", System.currentTimeMillis(), n); }
Using Java 11's HttpClient
private static void requestWithJava11HttpClient(String url1, String url2) { HttpRequest req1 = HttpRequest.newBuilder() .uri(URI.create(url1)) .timeout(Duration.ofMillis(5000)) .GET() .build(); HttpRequest req2 = HttpRequest.newBuilder() .uri(URI.create(url2)) .timeout(Duration.ofMillis(5000)) .GET() .build(); CompletableFuture> f1; f1 = m_javaClient.sendAsync(req1, HttpResponse.BodyHandlers.ofString()); CompletableFuture > f2; f2 = m_javaClient.sendAsync(req2, HttpResponse.BodyHandlers.ofString()); f1.thenCombine(f2, (s1, s2) -> { int n1 = Integer.parseInt(s1.body()); int n2 = Integer.parseInt(s2.body()); return (n1 + n2); }).thenAccept((n) -> { try { m_totalResponses.incrementAndGet(); System.out.printf("\nCurrentMS: %d, result: %d", System.currentTimeMillis(), n); } catch(Exception e) { e.printStackTrace(); } }); }
Testing method
Each method was called 500 times and followed by a gap of 5 seconds which was followed by another set of 500 calls, in total 2500 calls were made to both REST APIs. The REST API was written in Python and is as simple as:
@app.route('/n1', methods = ['GET']) def getN1(): return "1"; @app.route('/n2', methods = ['GET']) def getN2(): return "2";
Testing results
Test | Peak Threads | Total Time (ms) |
---|---|---|
Apache HttpClient (100 threads) | 111 | 35991 |
Apache HttpClient (1000 threads) | 1010 | 33956 |
Apache Async Http Client | 35793 | 20 |
Apache HttpClient (1000 threads) (JRE8) | 1019 | 36679 |
Java 11 Http Client | 413 | 22636 |
Conclusion
Java 11's native HttpClient is almost 50% better than using Apache's HttpClient as well as Async HttpClient. This in itself is a great reason to move to Java11 if you're still using Java 8. Code is available on GitHub.