Some RESTful JSON APIs impose limits on how often users can make API requests. Some APIs with rate limits set the status code of a response to 429 (Too Many Requests) when a user exceeds their rate limit and include a Retry-After header (or various X-RateLimit- headers) in the response to explicitly let us know how long we should wait before sending another request.
Information about an API's "rate limits" can usually be found in the documentation provided by the API developers or provider. If you are still unsure whether rate limits are imposed after consulting the API's documentation, then you can usually figure it out yourself using curl, a Unix command that lets you send an HTTP request in the terminal and see its associated HTTP response. For example, the following curl example would tell us that we need to wait at least 300 seconds (i.e., 5 minutes) before sending another request (see "man curl" for information about curl's options):
$ curl -IL -X GET 'REQUEST-URI'
HTTP/1.2 429 Too Many Requests
Retry-After: 300
You can access the headers associated with an HttpResponse<T> obect in in your Java code by calling the object's headers() method. After that, you can call getFirstValue(name) on the HttpHeaders object to access the value of a specific header entry where name is the lowercase version of the desired header's name. Here is a simple example (without error handling) that attempts to get, parse, and use the value of the"Retry-After" header associated with an HttpResponse<String> object:
Optional<String> retryAfter = response.headers()
.getFirstValue("retry-after");
if (rettryAfter.isPresent()) {
String retryAfterValue = retryAfter.get(); // "300"
int seconds = Integer.parseInt(retryAfterValue); // 300
...
} // ifSee the documentation for the headers() method in HttpResponse<T> for more information.
The Open Library API is nice because it does not require you to register with the API provider to get an API key. According to the API documentation, the endpoint URL is:
"https://openlibrary.org/api/search.json"
Basic usage includes what looks like three mutually exclusive
parameters, q, title, and author. The table below provides an
example for each parameter.
| Parameter | Example Value | Query String[1] |
|---|---|---|
q |
the lord of the rings |
?q=the+lord+of+the+rings |
title |
the lord of the rings |
?title=the+lord+of+the+rings |
author |
tolkien |
?author=tolkien |
Remember, the value in param=value contained in a query string
needs to be url-encoded by calling
URLEncoder.encode(value, StandardCharsets.UTF_8).
The API documentation says the JSON response should look something like this:
{
"start": 0,
"num_found": 629,
"docs": [
{...},
{...},
{...},
{...}
]
}The starter code includes an example that uses this API in OpenLibrarySearchApi.java. It models the JSON-formatted response string using two Java classes, gets the string using an HTTP client, and parses the string using Gson. The example does little to no error checking.
TheDogApi requires you to register with the API provider to get an API, and that API key is required in order to make HTTP requests to the API's endpoint URL. See the API's signup page for information on how to register for a key.
Once you have your API key, you will want to store it in a .properties
file so that it's not hard-coded in your program. For example, you might
store the API key in resources/config.properties like this:
thedogapi.apikey=YOUR-API-KEY
An example of how to read the values from resources/config.properties
is provided in the starter code
here.
We will assume that your code retrieves your API key and stores it in a
string using some code similar to this:
final String apiKey = config.getProperty("thedogapi.apikey");According to the API documentation, the endpoint for TheDogApi is this URL:
"https://api.thedogapi.com/v1"
This API provides different "methods" that are accessed via different paths relative to the endpoint:
"https://api.thedogapi.com/v1/method/name"
The table below provides an example for some of he methods; you should consult the API documentation for information about other potential methods:
| Method | Description | Query String |
|---|---|---|
/breeds |
List the Breeds | ?api_key=KEY |
/breeds/search |
Search for Breeds | ?api_key=KEY&q=golden |
Remember, the value in param=value contained in a query string
needs to be url-encoded by calling
URLEncoder.encode(value, StandardCharsets.UTF_8).
Below is an example of what the JSON response for the /breeds method
looks like. Notice how the outer-most element refers to a JSON array and
not a JSON object. This particular response contains an array of
objects:
[
{...},
{
...
"id": 1,
"name": "Affenpinscher",
"bred_for": "Small rodent hunting, lapdog",
"breed_group": "Toy",
"life_span": "10 - 12 years",
"temperament": "Stubborn, Curious, Playful, Adventurous, Active, Fun-loving",
"origin": "Germany, France",
"reference_image_id": "BJa4kxc4X",
"image": {
"id": "BJa4kxc4X",
"width": 1600,
"height": 1199,
"url": "https://cdn2.thedogapi.com/images/BJa4kxc4X.jpg"
}
},
{...},
...
{...},
]To use this API, model the JSON-formatted response body string using Java classes, get the string using an HTTP client, then parse the string using Gson.
Since the outermost portion of the response is an array of objects,
you will need to use ClassName[].class instead of
ClassName.class when using Gson's fromJson method. Assuming
Breed is the name of the class used to model each object in the
array:
public class BreedImage {
...
String url;
} // BreedImagepublic class Breed {
...
String name;
...
String origin;
...
BreedImage image;
} // Breed// ELSEWHERE
Breed[] breeds = GSON.fromJson(responseBody, Breed[].class);The variable life_span does not conform to the class code style
guidelines. To model a response with such a variable, make use of
Gson's @SerializedName annotation as follows:
@SerializedName("life_span")
String lifeSpan;This will tell Gson that the variable is named life_span in the
JSON response and lifeSpan in the Java object, the latter of which
conforms to the class code style guidelines. To use the
@SerializedName annotation, import
com.google.gson.annotations.SerializedName.
Consider the following JSON responses:
{
"releases": {
"na": "...",
"eu": "..."
}
}
{
"releases": {
"jp": "...",
"eu": "..."
}
}Since the releases refers to an object with variable names that vary,
a Map<K, V> is needed. A Map<K, V> is a mapping from "keys" (i.e.,
variable names) to "values" (i.e., the values of the keys / variables).
To model this in a class that can be used by Gson, declare releases as
a Map<String, String> variable as follows:
Map<String, String> releases;To use Map<K, V>, import java.util.Map. To access the data in the
releases variable, interact with it using the methods available in the
Map interface:
public class ExampleResponse {
Map<String, String> releases;
} // ExampleResponse// ELSEWHERE
ExampleResponse obj = GSON.fromJson(responseBody, ExampleResponse.class);
Map<String, String> releases = obj.releases;
if (releases.contains("na")) {
// use releases.get("na") to get its value
} // ifIn the example above, K repreents the "key" type and is replaced
with String. The keys in this mapping are the variable names that
vary (e.g., "na", "eu", "jp", etc.). Likewise, V represent
the "value" type and is also replaced with String. If the values
in your response are a different type, then replace V with
something else.
A TypeToken object is needed when a Map<K, V> is required to
model the outer-most object instead of some inner object. Consider
the following JSON responses:
{
"CSCI 1301": { ... },
"CSCI 1302": { ... }
}{
"CSCI 1302": { ... },
"CSCI 2720": { ... }
}Now suppose you have a class called ValueType that models the
{ ... } values. You may be tempted to try the following, which
will NOT work:
Map<String, ValueType> map = GSON.fromJson(responseBody, Map<String, ValueType>.class);To get this to work, a TypeToken object is neede. A TypeToken is
a Gson-specific object that can help Gson remember the type
information in a scenario like this. A small example is provided
below -- be sure to replace ValueType with the name of class you
want to use to model the values:
TypeToken<Map<String, ValueType>> mapType = new TypeToken<Map<String, ValueType>() {};
Map<String, ValueType> map = GSON.fromJson(responseBody, mapType);The FQN for TypeToken is com.google.gson.reflect.TypeToken.
Copyright © Michael E. Cotterell, Bradley J. Barnes, and the University of Georgia. This work is licensed under a CC BY-NC-ND 4.0_ license to students and the public. The content and opinions expressed on this Web page do not necessarily reflect the views of nor are they endorsed by the University of Georgia or the University System of Georgia.