Tech Blog

Calling APIs in parallel with Java code

The Alma REST APIs return JSON natively. This is the natural choice for developers working in dynamic languages, such as Node.js and Ruby. These languages interpret the JSON into native types, such as Array, Hash, String, and make it easy to manipulate data returned by the APIs.

Below we can see an example of updating users information using Alma’s APIs:

We’ll GET user information into a JSON object, update the object and writing it back with a PUT request. (error handling removed for brevity).

public static void updateUser(String userId) {
        String url = BASE_URL + userId + API_KEY;

        HttpResponse outGetReq = sendHttpReqAndCheckThresholds(url, "GET", null);

        JSONObject jsonObject = new JSONObject(outGetReq.getBody());

        // PUT to change middle_name
        jsonObject.put("middle_name", "new middle name");
        HttpResponse outPutReq = sendHttpReqAndCheckThresholds(url, "PUT", jsonObject.toString());
    }

The method sendHttpReqAndCheckThresholds() handles:

  • Retry in case the HTTP request returned 500 (Internal Server Error) or 429 (Too Many Requests per sec). In both cases we’ll retry RETRY_TIMES number of times.
  • We wait before running the request again 1-5 seconds randomly. Randomly, because we don’t want all threads to retry at the same time at the 2nd attempt, and possibly receiving 429 again and again.
  • After the request succeeds it runs checkThresholds() which is explained below.
private static HttpResponse sendHttpReqAndCheckThresholds(String url, String method, String body) {
        HttpResponse httpResponse = null;
        for (int i = 0; i < RETRY_TIMES; i++) {
            httpResponse = sendHttpReq(url, method, body);
            if (httpResponse.getResponseCode() != HttpsURLConnection.HTTP_INTERNAL_ERROR
                    && httpResponse.getResponseCode() != HTTP_TOO_MANY_REQ) {
                // request was successful - no need to retry
                break;
            }
            logger.info("HTTP Code is : " + httpResponse.getResponseCode() + " Retrying...");
            try {
                // wait 1-5 sec. and try again
                Thread.sleep((new Random().nextInt(5) + 1) * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        checkThresholds(httpResponse.getHeaders());
        return httpResponse;
    }
  • The number of requests left for the day is included in the response header X-Exl-Api-Remaining. We warn when reaching 80% and exit (all threads) when reaching 95%.
private static void checkThresholds(Map<String, List<String>> headersMap) {
        long apiRemaining = 0;
        try {
            apiRemaining = Long.parseLong(headersMap.get("X-Exl-Api-Remaining").get(0));
        } catch (Exception e) {
            return;
        }
        double remainingPercentage = 100 - (apiRemaining * 100D / MAX_API_REQ_PER_DAY);
        if (remainingPercentage >= 95D) {
            logger.error("Stopping - API Remaining - Not enough Api's for today ");
            Runtime.getRuntime().halt(0);
        }
        if (remainingPercentage >= 80D) {
            logger.warn("Warning - API Remaining - close to threshold (" + remainingPercentage + "%)");
        }
    }

To update a small amount of users you can write their usernames into the Java code itself. To update a larger amount of user, the list can be read from an input file. To speed up the processing we can run with up to 20 threads in parallel. It is not recommended to run with more than 20 threads since the API Gateway allows up to 25 requests per sec.

public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(MAX_NUM_OF_THREADS);
        // String userIds = "bdikazzz1 bdikaxxx1";
        // for (String userId : userIds.split(" ")) {
        for (String userId : readIdsFromFile()) {
            pool.execute(new Task(userId));
            // updateUser(userId); // sequential processing
        }
        logger.info("Done with main()");
    }

If you choose to read the list from an input file create a text file with primary-ids:

The full code is available in this Github repository.

Leave a Reply