API requests in Java
Do not miss this exclusive book on Binary Tree Problems. Get it now for free.
This article shows and explains the techniques to run API requests from Java applications, examples applied for GitHub REST API, GitHub GraphQL API, and public web services for SOAP API.
Application Programming Interfaces API are a way for one program to interact with another to share information or request services. This term is often used with web applications today, and you probably use it on daily basis without even noticing. For example, messengers on the phone can locate your current direction, and they do it by requesting the service of the google maps API.
So, the obvious profit of the API services is the ability to use already implemented and tested features to save time and money on development. But as there are so many services that are developed by different teams, how do they standardize API requests so it will be easier for both customers and developers? Three different approaches are mostly in use today:
- Simple Object Access Protocol SOAP bases communication entirely on the Extensible Markup Language (XML) documents
- Representational State Transfer REST is a software architectural style that usually transfers requests via URLs using HTTP protocol and standard HTTP methods like
GET
,POST
,PUT
,PATCH
,DELETE
- GraphQL - data query and manipulation language specifically developed for API requests
Table of contents:
- API requests to GitHub Rest API
- API requests to GitHub GraphQL API
- API requests to public SOAP API
- Conclusion
API requests to GitHub Rest API
Rest API defines several endpoints (URLs) that provide different services. In GitHub API, the request to endpoint https://api.github.com/
returns a list of all available endpoints. To ask the server for this data, one needs to write a GET API request.
GET HTTP method is used to request data from a specified resource.
GET API request with standard Java packages
In Java, it is possible to write HTTP requests with the standard IO and NET packages, as shown in the code below:
public class RequestGitHubApiService {
public static void main(String[] args) {
try {
// Create connection to the API end point, by default used GET method
URL address = new URL("https://api.github.com/");
HttpURLConnection connection = (HttpURLConnection) address.openConnection();
connection.connect();
// Set up input stream to read data from the server
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// Print server response to terminal
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
// Close input stream after work is done
in.close();
} catch (MalformedURLException e) {
System.out.println("Wrong URL address");
} catch (IOException e) {
System.out.println("Download error " + e.getMessage());
}
}
}
The GitHub API sends the list of API endpoints in response:
{
"current_user_url": "https://api.github.com/user",
"current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
"authorizations_url": "https://api.github.com/authorizations",
"code_search_url": "https://api.github.com/search/code?q={query},"
...
}
As requests grow in complexity and the returned data needs to be parsed, it is easier to use tools built specifically for running API requests: in Java, it is Unirest, OkHTTP, AsyncHttp or Java.net.Http client.
GET API request with Unirest tool
To run Unirest, add Unirest dependency in the Maven project as follows:
<dependency>
<groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId>
<version>3.13.6</version>
</dependency>
Now the same request to GitHub API as above is much easier to write and read:
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
public class RequestGitHubApiServiceWithUnirest {
public static void main(String[] args) {
// Send GET request to the server API
HttpResponse <String> httpResponse = Unirest.get("https://api.github.com/")
.asString(); // Return response as String
// Print server response to terminal
System.out.println(httpResponse.getBody());
}
}
When running multiple API requests with Unirest, it is useful to set default headers and other properties like timeouts, enable or disable cookies, and base URL via
Unirest.config
. It is also a good practice toUnirest.shutdown();
after the work is finished.
Authentication to GitHub API
Now, the GitHub API responses, but so far there is not much difference with simple opening URL with a web browser. What else can be done with API?
To access more services via API, the user needs to go through the process of authentication. With GitHub API, the Personal access tokens can be used. You can generate new tokens with different access rights and expiration times, the generated token value looks like this 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
. Note: it is better to be careful with token sharing as it gives access to your account (like a password).
One way to test if the GitHub API accepts authentication is to send a GET request to the https://api.github.com/user
endpoint. The GET request should include a header with the user authentication token. In the example below I also changed the HttpResponse type from String to JSON for nicer formatting.
public class RequestGitHubApiServiceAuth {
public static void main(String[] args) {
// Check the authentication - endpoint should return User Name
HttpResponse <JsonNode> httpResponse = Unirest.get("https://api.github.com/user")
// Add header "Authorization: Bearer <token>"
.header("Authorization", "Bearer " + "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")
.asJson(); // Return server response mapped to JSON format
// Print server response to terminal
System.out.println(httpResponse
.getBody() // Body of the server response
.getObject() // Get JSON object from body
.toString(4)); // Get formatted string from JSON object
}
}
The server response returns user information which indicates that API accepts authentication:
{
"login": "AnnaBurd",
"id": 91190871,
"node_id": "MDQ6VXNlcjkxMTkwODcx",
...
}
POST API request to create new repository
Some of the GitHub API endpoints request additional data transferred via a POST request. For example, endpoint https://api.github.com/user/repos
for creating a new repository.
POST HTTP method is used to send data to a server to create/update a resource.
public class RequestGitHubApiServiceCreateRepository {
public static void main(String[] args) {
// Send POST request to the API endpoint
HttpResponse <JsonNode> httpResponse = Unirest.post("https://api.github.com/user/repos")
// Add header "Authorization: Bearer <token>"
.header("Authorization", "Bearer " + "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")
// Attach request parameters in JSON format to body
.body("{\"name\":\"my-new-repository\"," // repository name (required)
+ "\"private\":\"true\", " // private repository
+ "\"auto_init\":\"true\"}") // initialize repository with a minimal README
.asJson(); // Return server response mapped to JSON format
}
}
The new repository was successfully created:
PUT API request to upload new file
So far the new repository is empty, it is time to upload a new file via an API request. For this, the endpoint https://api.github.com/repos
is used, and the request keyword is PUT.
PUT HTTP method is used to send data to a server to create/update a resource. The difference from the POST method is that POST creates a child resource at a server-defined URL, and in the PUT method, the whole URL is client-defined.
As the GitHub API requires file content to be encoded using Base64, I used a standard encoder from Java.util package.
public class RequestGitHubApiServiceUploadFile {
public static void main(String[] args) {
// Send PUT request to API endpoint
// https://api.github.com/repos/<User>/<repository>/contents/<filename>
HttpResponse <JsonNode> httpResponse = Unirest.put("https://api.github.com/repos" +
"/AnnaBurd/my-new-repository/contents/myfile.txt")
.header("Authorization", "Bearer " + "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") // Authenticate
// Attach parameters in JSON format to body
.body("{\"message\":\"uploaded with API\"," // The commit message
+ "\"content\":\"" +
// The new file content Base64 encoded
Base64.getMimeEncoder().encodeToString("Hello From API".getBytes())
+ "\"}")
.asJson(); // Return server response mapped to JSON format
}
}
The file was uploaded to repository:
PUT API request to reupload file
With similar to upload PUT requests, GitHub API allows changing existing file content. An additional required parameter is the sha (Secure Hash Algorithm) value of the file, and the easiest way to get it is to request it from GitHub itself.
public class RequestGitHubApiServiceChangeFile {
public static void main(String[] args) {
// Send GET request to access existing file SHA
HttpResponse <JsonNode> httpResponse = Unirest.get("https://api.github.com/repos/"
+ "AnnaBurd/my-new-repository/contents/myfile.txt") // File URL
.header("Authorization", "Bearer " + "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") // Authenticate
.asJson();
// Parse response to get sha value (sha looks like this 606663d31749b3ed228222729192effc14edcb85)
String sha = httpResponse.getBody().getObject().get("sha").toString();
// Send PUT request with new file content and sha attached to the JSON body
httpResponse = Unirest.put("https://api.github.com/repos" +
"/AnnaBurd/my-new-repository/contents/myfile.txt")
.header("Authorization", "Bearer " + "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") // Authenticate
// Attach parameters in JSON format to body
.body("{\"message\":\"reuploaded with API\"," // The commit message
+ "\"content\":\"" +
// The updated file content, using Base64 encoding
Base64.getMimeEncoder().encodeToString("Hello From API again".getBytes())
+ "\",\"sha\":\"" + sha +"\"}")
.asJson(); // Return server response mapped to JSON format
}
}
The file content was changed:
GET API request to retrieve file content
To download file content, the same endpoint https://api.github.com/repos/
can be used as well, but the type of request is GET.
As the content of the file in GitHub is encoded with Base64, to read it the decoder must be applied.
public class RequestGitHubApiServiceDownloadFile {
public static void main(String[] args) {
// Send GET API request to access existing file content
HttpResponse <JsonNode> httpResponse = Unirest.get("https://api.github.com/repos/"
+ "AnnaBurd/my-new-repository/contents/myfile.txt") // File URL
.header("Authorization", "Bearer " + "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") // Authorize
.asJson();
// Parse response to get file content value
String content = httpResponse.getBody().getObject().get("content").toString();
// Value is encoded
System.out.println(content); // Output: SGVsbG8gRnJvbSBBUEkgYWdhaW4=
// Decode content
content = new String(Base64.getMimeDecoder().decode(content));
// Content value is the same as expected
System.out.println(content); // Output: Hello From API again
}
}
REST API overview
The REST API allows the use of multiple services via simple HTTP requests and different endpoints and thus enhances the development experience and helps to automate the workflow. The code examples show work with GitHub REST API, but in general, these techniques can be applied to any web service with REST API.
Some web services have additional wrapper libraries to make accessing API even easier. GitHub, for instance, has GitHub API for Java and JCabi GitHub API written by third-party developers.
With simple requests, REST API is easy to use from a client site, but it still is not flawless: there are problems of overfetching - server responses return many unnecessary data along with the needed one, and underfetching - collecting complex data requires writing multiple API requests. The newest language designed for API requests GraphQL tries to solve these problems by applying complex JSON-like schemes.
API requests to GitHub GraphQL API
While REST API has multiple endpoints for different services, GraphQL API has only one main endpoint to manage all requests. GitHub, for example, uses https://api.github.com/graphql
endpoint. The GET request to this endpoint will run an introspection query of the schema and return a rather complex schema of the GitHub GraphQL API.
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
},
"mutationType": {
"name": "Mutation"
},
"subscriptionType": null,
"types": [
{
"kind": "INPUT_OBJECT",
"name": "AbortQueuedMigrationsInput",
"description": "Autogenerated input type of AbortQueuedMigrations",
"fields": null,
"inputFields": [
... ------> GitHub GraphQL schema contains over 100000 lines
API request for current user login
With GraphQL API, each request is addressed to the main endpoint, the query is attached to the body of the request. The HTTP method is POST.
The simplest example is to ask the GitHub API server for the Login of the current user.
public class RequestGitHubGraphQL {
public static void main(String[] args) {
// POST request to the endpoint
HttpResponse<JsonNode> httpResponse = Unirest.post("https://api.github.com/graphql")
// Authenticate the same way as with REST API
.header("Authorization", "Bearer " + "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")
.body("{\"query\": \"{viewer {login}}\"}") // GraphQL query
.asJson();
// Print server response to the terminal
System.out.println(httpResponse.getBody().getObject().toString(4));
}
}
The server returns only the data that was asked for:
{
"data": {
"viewer": {
"login": "AnnaBurd"
}
}
}
To test requests for the GitHub GraphQL API online, Explorer comes helpful.
API request for file content
GitHub GraphQL allows to get content of existing file with a simple query:
{
repository(owner: "AnnaBurd", name: "my-new-repository") {
object(expression: "HEAD:myfile.txt") {
... on Blob {
text
}
}
}
}
The same query in Java is harder to read as nested double quotes are escaped with triple slashes \\\"
:
public class RequestGitHubGraphQLFileContent {
public static void main(String[] args) {
// The GraphQL query as Java String - the GitHub API will parse it
String query = "{repository(owner: \\\"AnnaBurd\\\", name: \\\"my-new-repository\\\"){"
+ "object(expression: \\\"HEAD:myfile.txt\\\"){... on Blob {text}}}}";
HttpResponse<JsonNode> httpResponse = Unirest.post("https://api.github.com/graphql")
.header("Authorization", "Bearer " + "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") // Authenticate
.body("{\"query\": \"" + query + "\"}") // Attach GraphQL query to body
.asJson();
// Print server response to the terminal
System.out.println(httpResponse.getBody().getObject().toString(4));
}
}
The content of the file is returned wrapped in JSON format:
{
"data": {
"repository": {
"object": {
"text": "Hello From API again"
}
}
}
}
API request for file upload or reupload
Updating uploading new files can also be done with GraphQL, with GitHub API the task includes the following steps:
Step 1. Query the OID of the last commit to the branch, as it is required to make a commit. Example graphQL query is shown below:
{
repository(name: "my-new-repository", owner: "AnnaBurd") {
defaultBranchRef {
target {
... on Commit {
history(first: 1) {
nodes {
oid
}
}
}
}
}
}
}
Step 2. Use graphQL mutation called "CreateCommitOnBranchInput" to create a commit with new file content:
----------------------mutation ------------------
mutation ($input: CreateCommitOnBranchInput!) {
createCommitOnBranch(input: $input) {
commit {
url
}
}
}
-----------variables for mutation---------------
{
"input": {
"branch": {
"repositoryNameWithOwner": "AnnaBurd/my-new-repository",
"branchName": "main"
},
"message": {
"headline": "Hello from GraphQL!"
},
"fileChanges": {
"additions": [
{
"path": "myfile.txt",
"contents": "SGVsbG8gZnJvbSBKQVZBIGFuZCBHcmFwaFFM" <------- encoded base 64
}
]
},
"expectedHeadOid": "db7a5d870738bf11ce1fc115267d13406f5d0e76" <----- oid from step 1
}
}
GraphQL queries implementation in Java:
public class RequestGitHubGraphQLUploadFile {
public static void main(String[] args) {
// Step 1
// Format query to GitHub GraphQL API to get latest OID value for default branch (main)
String query = "{repository(name: \\\""
+ "my-new-repository" // Name of the repository
+ "\\\", owner: \\\""
+ "AnnaBurd" // Owner Login
+ "\\\"){defaultBranchRef{target{... on Commit{history(first: 1){nodes{oid}}}}}}}";
// Run query request to the server
HttpResponse<JsonNode> httpResponse = Unirest.post("https://api.github.com/graphql")
.header("Authorization", "Bearer " + "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") // Authenticate
.body("{\"query\": \"" + query + "\"}") // Send GraphQL query
.asJson();
// Get value from returned nested JSON object
JSONObject history = httpResponse.getBody()
.getObject()
.getJSONObject("data")
.getJSONObject("repository")
.getJSONObject("defaultBranchRef")
.getJSONObject("target")
.getJSONObject("history");
JSONArray nodes = history.getJSONArray("nodes");
String oid = nodes.getJSONObject(0).get("oid").toString();
System.out.println(oid); // Output: 1b3971a6f9f6cf42a6b0a7d2e3d8c315d609154a
// Step 2
// Format mutation query to GitHub GraphQL API to make a commit
query = "mutation ($input: CreateCommitOnBranchInput!){createCommitOnBranch(input: $input){commit{url}}}";
// Set up the mutation properties
String variables = "{\\\"input\\\": {\\\"branch\\\": {\\\"repositoryNameWithOwner\\\": \\\""
+ "AnnaBurd/my-new-repository" // Owner name and repository name
+ "\\\",\\\"branchName\\\": \\\""
+ "main" // Branch name
+ "\\\"},\\\"message\\\":{\\\"headline\\\": \\\""
+ "Hello from GraphQL!" // Commit message
+ "\\\"},\\\"fileChanges\\\":{\\\"additions\\\":[{\\\"path\\\": \\\""
+ "myfile.txt" // File name
+ "\\\",\\\"contents\\\": \\\""
+ "SGVsbG8gZnJvbSBKQVZBIGFuZCBHcmFwaFFM" // File content - Base 64 encoded
+ "\\\"}]},\\\"expectedHeadOid\\\": \\\""
+ oid // OID of last commit to the branch
+ "\\\"}}";
// Commit new file content to the gitHub GraphQL API
httpResponse = Unirest.post("https://api.github.com/graphql")
.header("Authorization", "Bearer " + "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") // Authenticate
.body("{\"query\": \""+query+"\", \"variables\": \"" + variables + "\"}") // GraphQL query
.asJson();
}
}
The file was reuploaded to the GitHub repository with new commit:
GraphQL API overview
Overall, it is possible to write requests to GraphQL API from Java, but formatting and writing correct queries is not a trivial task, so for simple cases, it is easier to work with REST API.
The GraphQL and REST API requests are compatible with Java applications, the last part of the article shows an example of the request to more old SOAP API. GitHub does not support one, so I used a free public API for example.
API requests to public SOAP API
SOAP API is similar to REST because it uses multiple endpoints to provide services, and it is similar to GraphQL because it requires a special query attached to the request body, but this time in XML format.
The example below shows a simple request to the public API service that returns the link to the country flag based on the country code. It is easy to use if the service provider shares XML templates to put parameters into.
public class RequestSoapAPI {
public static void main(String[] args) {
// For a SOAP request, first the URL of the service host is required
String url = "http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso";
// And the second is the file or String in XML format that wraps request parameters
String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<soap12:Envelope xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">"
+ "<soap12:Body>"
+ "<CountryFlag xmlns=\"http://www.oorsprong.org/websamples.countryinfo\">"
+ "<sCountryISOCode>VN</sCountryISOCode>" // Request parameter - country code VN
+ "</CountryFlag>"
+ "</soap12:Body>"
+ "</soap12:Envelope>";
// Send POST request to the server and attach XML to the body of the request
HttpResponse <String> httpResponse = Unirest.post(url)
.header("Content-Type", "text/xml; charset=utf-8") // Implicitly indicate the body content type
.body(xml)
.asString();
// The response contains the XML file with the requested info
System.out.println(httpResponse.getBody());
}
}
If the request was correct, SOAP API server sends response with XML file:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:CountryFlagResponse xmlns:m="http://www.oorsprong.org/websamples.countryinfo">
---> the server response - link to the flag of VN
<m:CountryFlagResult>http://www.oorsprong.org/WebSamples.CountryInfo/Flags/Vietnam.jpg</m:CountryFlagResult>
</m:CountryFlagResponse>
</soap:Body>
</soap:Envelope>
Conclusion
API aims to provide the ability to communicate between applications and services regardless of how that services were implemented or what programming languages were used. There are different API standards: some are relatively old and are gradually getting replaced by new popular solutions but whatever the choice of API is, Java will support it.
Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.