Introduction
In this tutorial, we use the Google Translate API (version two) on RapidAPI using a Java command-line client. We program the client as a Spring Boot application and use three different Java REST libraries – the Eclipse Jersey implementation of the Java API for RESTful Web Services (JAX-RS), the Unirest-Java library, and the OkHttp library. Each library calls all three Google Translate endpoints – languages, detect, and translate. By the end of the tutorial, you will know how to use Java to get Google translate’s supported languages, identify a text’s language, and translate between two languages. But along the way, we will also model the application using the Unified Modeling Language (UML), use the Jackson library to serialize/deserialize JSON, and learn how to write a Spring Boot application as a console program rather than a web application.
This tutorial uses a wholistic pedagogy and follows a mini-development life-cycle; we start by reviewing the API on RapidAPI. We then model the application we will build that interacts with the API, followed by incrementally developing the application. If only interested in the implemented client code, then feel free to jump to the sections presenting the complete code for the JaxRsTutorialRestClientImpl
, UnirestTutorialRestClientImpl
, and OkHttpTutorialRestClientImpl
classes. However, you might gain some valuable information by following the “mini life-cycle” pedagogy of this tutorial, even if a more experienced developer. But rest assured, the tutorial’s title is not click-bait. In this tutorial, you learn how to use Java to write a client that accesses all three Google Translate API endpoints via RapidAPI. Okay, enough description of what we are about to accomplish, we have a lot to discuss, let’s get started by briefly reviewing RapidAPI.
RapidAPI
RapidAPI is an online repository of different REST APIs from many different sources. It provides a single location for the discovery and management of various REST APIs and provides a single source of registration and billing. RapidAPI started as a GitHub repository and has grown to over 10,000 APIs. RapidAPI does not host these APIs, but rather, provides a single location you can develop, manage, and access those disparate APIs. As more companies monetize their RESTful APIs, trying to discover those APIs and manage them becomes increasingly unwieldy. As a developer who works daily with REST APIs, and as a tutorial author for RapidAPI by night, I can personally attest that RapidAPI truly does offer a valuable service for developers needing to incorporate RESTful APIs into his or her application. See the following article on DZone for more arguments for RapidAPI (RapidAPI Provides API MarketPlace and Insight).
- Here we focus on RapidAPI from an APIs consumer perspective. RapidAPI is also an excellent place to publish your API. Here is a tutorial on using RapidAPI as an API publisher (How to Build a Java RESTful API with Spring Boot). And here is RapidAPI documentation on publishing your API (The RapidAPI Developer Hub). See the sections API Provider Resources and Add Your API to RapidAPI.
We do not discuss getting set up on RapidAPI here; however, here are some links to references needed to get started. Before continuing this tutorial, you should register for a RapidAPI account if you have not already.
- If you do not have a RapdAPI account, follow these instructions to create an account (RapidAPI Quick Start Guide);
- Start at the following dashboard to access all RapidAPI documentation (The RapidAPI Developer Hub).
- RapidAPI’s YouTube channel also has much information to get started (RapidAPI).
Google Translate V2
Google’s Translate API is available on RapidAPI. The version on RapidAPI, as of the time of this tutorial, is version two. To use the API you must subscribe to it first, but don’t worry, the basic subscription is free. After subscribing, you should see the Test Endpoints
button replace the Subscribe to Test
button.
Endpoints
There are three exposed endpoints as of version two of the Google Translate API. The languages endpoint returns a list of languages supported by Google Translate, the detect endpoint detects the language of the specified text, and the translate endpoint translates text to a specified language. Let’s examine these three endpoints in more detail.
Languages
The languages endpoint returns a list of supported languages. It accepts a GET request that has two optional query string parameters, target and model. The endpoint returns a list of all supported languages, where the language value is the language code, and the name is the language’s name that was specified by the target query string parameter. If no target query string parameter is specified, then the name is omitted. The language’s code is the ISO 639-1 code (List of ISO 639-1 Codes). The target parameter’s value also uses a language’s ISO code. If the target language code is excluded, then the language names are omitted.
Let’s review the language endpoint by calling it and asking the results be returned in Chinese (Simplified). The request returns a list of language codes and names, as we specify the target language. After getting a list that includes names, we then exclude the target language from our request and obtain a list of language codes.
- Highlight the GET languages endpoint and enter
zh
as the target. - Click
Test Endpoint
, and the endpoint returns the languages with the name in Chinese (Simplified).
- Now delete
zh
from the target field and clickTest Endpoint
.
Detect Language
The detect endpoint takes a POST request with the text to analyze as a form field and returns the language it detects. Let’s use Chinese again, only this time as the language to detect.
RapidAPI Test Endpoint
- Navigate to the Google Translate website (https://translate.google.com), unless you speak and write Chinese, and enter some text and translate it to Chinese.
- Copy and paste the Chinese translation to the
q
field. - Click
Test Endpoint
, and the API detects the language aszh-CN
.
Translate
The last endpoint to review is translate. The translate endpoint accepts a POST request and sends the text to translate as a form field. There are four form fields accepted by this endpoint:
q
– the text to be translated,target
– the language code of the language to be translated to,model
– the translation model (optional – not discussed here),format
– the text format (optional – not discussed here),- and
source
– the language code of the language to be translated (optional).
In this tutorial, we use the q
, target
, and source
fields. The other two fields are optional and not discussed here.
RapidAPI Test Endpoint
- Highlight the translate endpoint and enter
en
as the source language andes
as the target. - Enter some text, in English, to the
q
field. - Click
Test Endpoint
, and the endpoint returns the text translated to Spanish.
Java Implementation: How do you use Google Translate API in your Java Application?
Now that we understand the three endpoints, we can write client Java code to call them. As mentioned in the introduction, here we use Spring Boot to develop our client application. There was no particular reason for my selecting Spring Boot, other than it is an easy way to free us from worrying about things such as project structure, creating a Maven pom file, and other tedious aspects involved in writing a non-trivial Java application. Because of this benefit of Spring Boot, we use it to develop a command-line application that runs directly in the JVM from the console and not in a web container. If you have never used Spring Boot to develop a console application, you will be surprised at how easy it is.
- Remember, we are writing a REST client application, it calls REST endpoints, there is no reason to build a web application in this tutorial.
Modeling the Application
Call me crazy – or bitter that all my training in the Rational Unified Process seems for naught in an Agile world – but I always design an application before developing it, even if it is a tractable tutorial application. I don’t get the design perfect, but that’s okay, as all I need is something that helps guide my development. And so, let’s design the application before beginning to code, keeping in mind our agile intentions behind modeling.
Our modeling efforts consist of writing a use-case diagram, a class diagram, and an object model diagram. The notation we use is UML, and we use the online tool, yUML, to create our diagrams. Again, our goal is not a complete formalized representation of the system we will develop, but rather, an informal model using UML that serves as a “hand-written map” providing us directions to our destination.
- The Agile Modeling website by Scott Ambler, author of The Object Primer 3rd Ed: Agile Model-Driven Development (AMDD) with UML 2, is an excellent resource for Agile Modeling (Agile Modeling).
- We use the lightweight online tool yUML to create our diagrams (yUML).
Let’s begin by considering the interactions between a user and the application we will develop by creating a use-case model. The use-case model will help us see the interactions and their relationships more clearly.
Use Case Model
A use-case model illustrates how users will interact with a proposed system. It is useful for determining the requirements, or functionality, your system must perform. Let’s create a use-case model depicting the interactions users perform when interacting with our application. Of course, our application is a wrapper around the Google Translate API, so it might be more proper to state that we are creating a use-case diagram of a user’s interactions with the Google Translate API. But whatever we call our modeling subject, let’s model it.
- More on use-case diagrams and using them in Agile modeling (UML 2 Use Case Diagrams)
Start by adding the user to the diagram. Of course, our application’s user is us, the developers, as we will be executing the application after developing it. And so we label the user as Developer. Our topmost interaction with our application is calling the Google Translate API. When we interact with the API, we call it using either the JAX-RS library, the Unirest-Java, or OkHttp library. As each sub-interaction is optional, we model these as extending the Call Google Translate interaction. But every call to Google Translate via RapidAPI, must include the RapidAPI required headers. As the getting RapidAPI headers interaction is required, we model this sub-interaction as being included by the Call Google Translate interaction.
When we interact with Google Translate using one of the three REST client libraries, we call all three REST endpoints. And so we model the languages, detect, and translate sub-interactions as included by the three REST library interactions. Now, we could have modeled each call as extending the REST library interaction. However, let’s assume that calling a REST library requires calling all three endpoints sequentially, and so we model them as being included. The following simple UML activity diagram illustrates our decision.
As our workflow illustrates, we conditionally call one of the three libraries to access the Google Translate API. We then use the selected library to call the languages, detect, and translate endpoints, respectively.
The use-case model provides a pretty good example of the user requirements our system must meet by having identified the user interactions with our proposed application. We can now create the following outline.
- Call the Google Translate APIs
- get the RapidAPI Headers
- Call using JAX-RS
- call the languages endpoint
- call the detect endpoint
- call the translate endpoint
- call using Unirest-Java
- call the languages endpoint
- call the detect endpoint
- call the translate endpoint
- call using OKHttp
- call the languages endpoint
- call the detect endpoint
- call the translate endpoint
The outline furthers our understanding by providing a list of the functionality we must provide when we develop our application. Okay, now that we have a good understanding of the user interactions let’s focus on our application’s class design.
Application Class Diagram
A class diagram describes a system’s structure by illustrating its classes, their relationships, and their internal properties and methods. The diagram is especially useful when we first start coding our application, as we can create a bare-bones skeleton of our application derived from this model which we will then add functionality (functionality derived from the use-case model).
- Refer to the following webpage for more on class diagrams and using them for Agile modeling (UML 2 Class Diagrams).
The use-case model and outline argues that we create three classes (one for each REST client library) used for calling the Google Translate REST API. Name the classes JaxRsTutorialRestClientImpl
, OkHttpTutorialRestClientImpl
and UnirestTutorialRestClientImpl
. However, the use-case model also illustrated that all that all three classes will use the same header variables. Moreover, all three clients call the same three REST endpoints. And so, keeping these two things in mind, we surmise that we should develop either an interface or an abstract class for these three classes to extend.
Let’s model the three classes as extending an abstract class we call TutorialRestClient
. In that class, we declare three abstract methods: getSupportedLanguages
, detectLanguage
and translate
. Later, when we code, we write concrete method implementations that override these three abstract methods. As this is a relatively simple application, we can now model our class diagram as three classes that extend the abstract TutorialRestClient
class.
- For more on abstract Java classes, refer to the Java Tutorial (Abstract Methods and Classes).
Note that the decision to create an abstract class extended by three concrete classes is a design decision; the decision is not the “correct” way to design our application. There are also good arguments for TutorialRestClient
being an interface; however, let’s keep our design decision and make TutorialRestClient
an abstract class.
Data Object Model
The REST endpoints return JSON resources from each of the three endpoints. When writing a Java application, it usually makes sense to convert these JSON string representations of a resource to a corresponding plain old Java object (POJO). One technique we can use to model these JSON resources as POJOs is by creating an Object Diagram that models the data objects an application will use as classes.
- Although we model the data objects manually, an alternative technique is to use a tool such as jsonschema2pojo to generate the classes for you (jsonschema2pojo). For an example, see this tutorial, also on RapidAPI (How to Build a Java RESTful API with Spring Boot).
Return to the RapidAPI page and review the JSON returned from the three endpoints. Notice that all three endpoints wrap their particular JSON response with an opening and closing brace, thus defining an anonymous resource.
- Remember, JSON defines “objects” as resources. JSON defines aggregations of objects of the same type as collections. For more on this naming, refer to the tutorial How to Build a Java RESTful API with Spring Boot, which discusses resources, collections, and objects in more detail.
Name the top-level anonymous resource GoogleTranslateObject
.
After defining the top-level resource, we identify the nested resource. Notice that all three endpoints also wraps its particular JSON resource in a data
resource. And so, we define a Data
object to represent the data
resource.
After defining the data
resource as a Data
class, we examine each endpoint’s nested resources. The languages
endpoint nests a collection of anonymous resources within the data
resource. The collection is languages
, while the individual resources comprising the collection are anonymous. But ignore the collection for now and name the individual entities comprising the collection as Language
objects. As you will see, we implement collections using a Java collections class.
Model the detect
endpoint’s resource as a Detection
. But note the detection
endpoint is different from the languages
endpoint by nesting an anonymous collection in a detections
collection. As we will see, rather than returning a list containing a resource (List<Language>
), we will return a nested list containing a resource (List<List<Detection>>
).
The translate endpoint’s collection is named translations
and contains anonymous resources that we call a Translation
object.
We now determine the relationship between the resources. All three endpoints nest a data resource in the top-level anonymous resource; a GoogleTranslateObject
is a collection of Data
objects. Each endpoint contains a collection of its particular resource, and so we model each of the collections as an aggregation of the specific resource identified above. Note that there is an intermediate collection for the Detection
resource (a collection of a collection of detection
resources). We also add the properties of each JSON resource by listing the property name in the object.
We now translate the class diagram, combined with the object model and the outline from our use-case model, as our application’s skeleton; however, before we do, let’s return to Spring Boot and create our application’s foundation.
Project Setup
Navigate to the Spring Initializr web page and create our application. Through Spring Initializr, we define our classpath, add the project’s required dependencies, and set other settings needed by the application, such as if we will package the application as an executable Jar or as a War. Spring Initializr saves us considerable effort when creating our application by performing these tasks for us and providing a zip file containing our configured application.
- For a more detailed introduction to using Spring Initializr, refer to the following tutorial (Building an Application with Spring Boot).
Spring Initializr
- Navigate to the Spring Initializr application (start.spring.io).
- Initialize the application to have the settings in the following screenshot.
- Add the Jersey dependency to the project.
Creating a project in Spring Initializr.
- Click
GENERATE
and download the resulting zip file. - Unzip the file to the desired location.
- As we will not use JUnit tests here, open the pom file, and remove the unit testing dependency.
- Delete the tests folder and subfolders from the project.
We now have the following pom file.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example.translate.rest.api</groupId> <artifactId>tutorial</artifactId> <version>0.0.1-SNAPSHOT</version> <name>TranslateClient</name> <description>Client application using Google Translate on RapidAPI</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Spring Boot Application Class
One of the classes Initializr creates for us is the TranlateClientApplication
class. However, by default, Spring Boot generates a web application. Moreover, when choosing to package our application as a jar, Spring Boot embeds a Tomcat Web Server and starts it when the application starts. But we don’t need this behavior. Instead, we want our application to run as a command-line application. To do this, we must modify the TranslateClientApplications
, so that it implements the Spring CommandLineRunner
interface, and we must also instruct Spring not to start the application as a web application. Let’s first change TranslateClientApplication
to implement the CommandLineRunner
interface.
- Modify
TranslateClientApplication
to implement the SpringCommandLineRunner
interface. - Add the
run
method to the class. - Modify
main
to not start a web container and to call therun
method. - Add a simple
System.out.println
statement torun
so we can test our changes immediately.
package com.example.translate.rest.api.tutorial; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.annotation.ComponentScan; @ComponentScan("com.example.translate.rest.api.clients") @SpringBootApplication public class TranslateClientApplication implements CommandLineRunner { @Autowired JaxRsTutorialRestClientImpl jaxClient; public static void main(String[] args) { new SpringApplicationBuilder(TranslateClientApplication.class) .web(WebApplicationType.NONE) .run(args); } @Override public void run(String... args) { System.out.println("works"); } }
- Use Maven to compile and package the application as an executable jar.
mvn clean package
- Run the application to ensure we modified everything correctly.
java -jar tutorial-0.0.1-SNAPSHOT.jar
Spring Boot Properties File
Now let’s modify the application.properties
file to set our application’s properties. Adding these properties to the properties file allows us to use these values in our application without hard-coding them in our Java classes.
- Add a setting telling Spring Boot not to create a web application (note that this is not necessary, as we specified to not build a web application through code in the
TutorialClientApplication
class). - Specify Spring not print a banner to the console on startup.
- Add the Google API endpoint URLs.
- Add the RapidAPI header keys and values.
We now have the following application.properties
file.
spring.main.web-application-type=NONE spring.main.banner-mode=off google.endpoint.base=https://google-translate1.p.rapidapi.com google.endpoint.languages=/language/translate/v2/languages google.endpoint.detect=/language/translate/v2/detect google.endpoint.translate=/language/translate/v2 com.rapidapi.rapid_api_key=X-RapidAPI-Key com.rapidapi.rapid_api_key_value=<your-key-here> com.rapidapi.rapid_api_host=x-rapidapi-host com.rapidapi.rapid_api_host_value=google-translate1.p.rapidapi.com
JSON Data Objects
Return to the model and let’s translate the object model we created earlier to create the needed data objects.
GoogleTranslateObject
- Create the
com.example.translate.rest.api.json.object
package. - Create a class named
GoogleTranslateObject
. - Create a class named
Data
. - Add
Data
as a property to theGoogleTranslateObject
class and create the associated access methodsgetData
andsetData
(getter and setter) for the property. - Add a
toString
method that uses the Jackson JSON library to printGoogleTranslateObject
as a JSON string.
package com.example.translate.rest.api.json.object; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; public class GoogleTranslateObject { private Data data; public Data getData() { return data; } public void setData(Data data) { this.data = data; } public String toString() { try { ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); return mapper.writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return null; } } }
We use the Jackson library to serialize and deserialize the data objects between JSON and POJOs. For Jackson to accomplish this task, it is essential to create the get<property name capitalized>
and the set<property name capitalized>
methods for every property present in the JSON resource.
Data
A Data
object consists of a list of a Language
, Detection
, or Translation
object. However, recall that the detection
resource is nested in two lists, and so we implement it as a nested list (List<List<Detection>>
). Create the Data
class, and as the Data
class relies upon the Language
, Detection
, and Translation
classes, create empty preliminary versions of those classes.
- Create the
Language
,Detection
, andTranslation
classes. - Add a list of
Language
objects, a nested list ofDetection
objects, and a list ofTranslation
objects as properties. - Create access methods for each property.
package com.example.translate.rest.api.json.object; import java.util.List; public class Data { private List<Language> languages = null; private List<List<Detection>> detections = null; private List<Translation> translations = null; public List<Language> getLanguages() { return languages; } public void setLanguages(List<Language> languages) { this.languages = languages; } public List<List<Detection>> getDetections() { return detections; } public void setDetections(List<List<Detection>> detections) { this.detections = detections; } public List<Translation> getTranslations() { return translations; } public void setTranslations(List<Translation> translations) { this.translations = translations; } }
Language
Now let’s create the Language
data object.
- Modify the
Language
class to have thelanguage
andname
properties, where both are strings. - Add the both property’s access methods.
- Implement the
toString
method to printLanguage
as a JSON string.
package com.example.translate.rest.api.json.object; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; public class Language { public String language; public String name; public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { try { ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); return mapper.writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return null; } } }
Detection
The detection
resource consists of a boolean value indicating if the detection is reliable, the detected language, and a confidence score. As an aside, it is my observation that Google Translate always guesses the language correctly, but invariably has little to no confidence in its translation ability (reliable is false, and the confidence score is low). Let’s finish the Detection
class.
- Add the
isReliable
,language
, andconfidence
properties, whereisReliable
is aBoolean
,confidence
is anInteger
, andlanguage
is aString
. - Add the associated access methods for the properties.
package com.example.translate.rest.api.json.object; public class Detection { private Boolean isReliable; private String language; private Integer confidence; public Boolean getIsReliable() { return isReliable; } public void setIsReliable(Boolean isReliable) { this.isReliable = isReliable; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public Integer getConfidence() { return confidence; } public void setConfidence(Integer confidence) { this.confidence = confidence; } }
Translation
A Translation
consists of the translated text and, if source language was not specified, the detected source language.
- Modify
Translation
to have atranslatedText
property. - Implement the
toString
method to printTranslation
as a JSON string.
package com.example.translate.rest.api.json.object; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; public class Translation { public String translatedText; public String detectedSourceLanguage; public String getDetectedSourceLanguage() { return detectedSourceLanguage; } public void setDetectedSourceLanguage(String detectedSourceLanguage) { this.detectedSourceLanguage = detectedSourceLanguage; } public String getTranslatedText() { return translatedText; } public void setTranslatedText(String translatedText) { this.translatedText = translatedText; } public String toString() { try { ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); return mapper.writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return null; } } }
- Use Maven to build the project, just to ensure everything is correct.
Component Classes
Now that we have the data objects coded let’s create the classes from our class diagram. Begin with the TutorialRestClient
class.
TutorialRestClient Abstract Class
The TutorialRestClient
class is abstract and declares three methods that call Google Translate’s three REST endpoints. The class also contains the shared headers and properties needed by the three child classes.
- Create a new package
com.example.translate.rest.api.clients
and create theTutorialRestClient
class as an abstract class. - Add the values in
application.properties
to the class using the@Value
annotation. - Create the
getSupportedLanguages
,detectLanguage
, andtranslate
methods as abstract methods.
package com.example.translate.rest.api.clients; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; public abstract class TutorialRestClient { @Value("${google.endpoint.base}") protected String endpointBase; @Value("${google.endpoint.languages}") protected String languagesEndpoint; @Value("${google.endpoint.detect}") protected String detectEndpoint; @Value("${google.endpoint.translate}") protected String translateEndpoint; @Value("${com.rapidapi.rapid_api_key}") protected String rapidApiKey; @Value("${com.rapidapi.rapid_api_key_value}") protected String rapidApiKeyValue; @Value("${com.rapidapi.rapid_api_host}") protected String rapidApiHost; @Value("${com.rapidapi.rapid_api_host_value}") protected String rapidApiHostValue; /** * Get supported languages from Google API V2 * @param targetLanguage * @return */ public abstract List<Language> getSupportedLanguages(String targetLanguage); /** * Use Google API V2 to detect language used on a string * @param inputString * @return */ public abstract List<Detection> detectLanguage(String inputString); /** * Use Google API V2 to translate from a language to another language * @param sourceLanguageCode * @return */ public abstract List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage); }
The class contains the three abstract methods we will override in the three child classes. The getSupportedLanguage
and translate
methods return a List
aggregating the specific data objects, while the detectLanguage
returns a nested list. TutorialRestClient
also includes the properties we need using the Spring @Value
annotation. Although this tutorial does not discuss Spring concepts in-depth, let’s pause to consider the @Value
annotation.
@Value Annotation
The Spring @Value
annotation indicates a default value for a field. We also added placeholders, ${<property name>}
, that tells Spring to replace the placeholders with the values of the environment properties. When starting Spring, the startup workflow is as follows,
- The
application.properties
file is loaded at startup, - the property values are added to the environment,
- the property values replace the placeholders
- and the value annotation sets the value as the property’s default value.
JaxRsTutorialRestClientImpl Class
Before moving to individually developing the logic in each implementation class, let’s create all three classes and have the concrete methods overriding the abstract methods in TutorialRestClient
return null
values. We do this so so we don’t write a bunch of code all at once that doesn’t compile. Instead, we take baby-steps and make sure the application works each step of the way.
- Note, the Spring @Component annotation we use below tells Spring this class is to be instantiated as a singleton at startup.
Let’s start with the JaxRsTutorialRestClientImpl
class.
- Create the
JaxRsTutorialRestClientImpl
class and extendTutorialRestClient
. - Add concrete implementations of the three abstract methods and have each return
null
.
package com.example.translate.rest.api.clients; import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; @Component public class JaxRsTutorialRestClientImpl extends TutorialRestClient { @Override public List<Language> getSupportedLanguages(String targetLanguage) { System.out.println(this.endpointBase + this.detectEndpoint); return null; } @Override public List<Detection> detectLanguage(String inputString) { return null; } @Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { return null; } }
OkHttpTutorialRestClientImpl Class
- Create the
OkHttpTutorialRestClientImpl
class and override the three abstract methods, as we did with the previous class.
package com.example.translate.rest.api.clients; import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; @Component public class OkHttpTutorialRestClientImpl extends TutorialRestClient { @Override public List<Language> getSupportedLanguages(String targetLanguage) { return null; } @Override public List<Detection> detectLanguage(String inputString) { return null; } @Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { return null; } }
UnirestTutorialRestClientImpl Class
- Repeat the steps used to create the previous two-component classes.
package com.example.translate.rest.api.clients; import java.util.List; import org.springframework.stereotype.Component; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; @Component public class UnirestTutorialRestClientImpl extends TutorialRestClient { @Override public List<Language> getSupportedLanguages(String targetLanguage) { return null; } @Override public List<Detection> detectLanguage(String inputString) { return null; } @Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { return null; } }
- Ensure we coded everything correctly and build the application using Maven.
- Run the application from the command-line.
Now that we have all three bare-bone classes let’s implement each class.
- You might wonder why we exclude Spring’s
RestTemplate
as one of the REST client libraries in this tutorial. The reasoning behind this exclusion is three-fold. First, as this tutorial is already long enough, the Spring implementation is left to the reader as an exercise. Second,RestTemplate
is replaced by theWebClient
in the latest Spring version, so you should use that class rather than a template. And third, using Spring to access a REST endpoint is well-documented compared to the other three libraries.
Endpoint Implementations
The first class we implement is the Eclipse Jersey implementation of the JAX-RS specification. We then code the class using the Unirest-Java library, followed by the class using the OkHttp library.
JAX-RS (JaxRsTutorialRestClientImpl)
As JAX-RS is the accepted standard with overwhelming industry support, we begin by implementing the endpoints in our JaxRsTutorialRestClientImpl
class.
- JAX-RS Java Documentation (Java Enterprise Edition 7 JavaDoc).
- Eclipse Jersey Documentation (Eclipse Jersey).
Languages (getSupportedLanguage)
- Add a
Client
andWebTarget
class and initialize them in apostConstruct
method. - Create a private method that adds the headers to a
MultiValuedMap
. - Add the
MultivaluedMap
as aqueryParam
to theWebTarget
.
Client client; WebTarget baseTarget; @PostConstruct private void postConstruct() { client = ClientBuilder.newClient(); baseTarget = client.target(this.endpointBase); } @Override public List<Language> getSupportedLanguages(String targetLanguage) { MultivaluedMap<String, Object> myHeaders = super.createRapidApiHeaders(); WebTarget languagesTarget = baseTarget.path(this.languagesEndpoint); GoogleTranslateObject translateObject = languagesTarget.queryParam("target", targetLanguage).request(MediaType.APPLICATION_JSON).headers(myHeaders).get(GoogleTranslateObject.class); return translateObject.getData().getLanguages(); } private MultivaluedMap<String,Object> createRapidApiHeaders() { MultivaluedMap<String, Object> myHeaders = new MultivaluedHashMap<String, Object>(); myHeaders.add(this.rapidApiHost, this.rapidApiHostValue); myHeaders.add(this.rapidApiKey, this.rapidApiKeyValue); return myHeaders; }
JAX-RS uses a MultiValuedMap
to create headers as a key/value pair. It defines a REST endpoint using a WebTarget
Interface instance. Recall that we are using the Eclipse Jersey implementation of JAX-RS, so behind the scenes, Jersey provides a concrete implementation of WebTarget
. After creating the target, we add the query parameter, the media type, and the headers to the target. We then execute the get
method, specifying that we expect a GoogleTranslateObject
as the return value. Of course, behind the scenes, Jersey obtains the response as a JSON string and uses the Jackson library to deserialize the string into a GoogleTranslateObject
instance. Finally, we get the List
of Language
instances from the response and return the List
instance.
Detect Language (detectLanguage)
Now let’s implement the detectLanguage
method.
- Create a
Form
instance and pass the text as a parameter. - Create a
WebTarget
, add the headers, and post theForm
instance as a request. - Return the nested
List
ofDetection
instances.
@Override public List<List<Detection>> detectLanguage(String inputString) { Form formData = new Form(); formData.param("q", inputString); WebTarget detectTarget = baseTarget.path(this.detectEndpoint); GoogleTranslateObject translateObject = detectTarget.request(MediaType.APPLICATION_JSON) .headers(super.createRapidApiHeaders()).post(Entity.form(formData), GoogleTranslateObject.class); return translateObject.getData().getDetections(); }
JAX-RS uses a Form
object to represent an HTML form-data request. We add the text as a parameter to the Form
instance and then use the Entity
object to pass the form to the post request. When we post the form, we also specify that we expect the response to be a GoogleTranslateObject
instance. We get the detections from the response and return the nested list of Detection
instances.
Translate
- Add conditional logic that detects if
sourceLanguage
is null. - If
sourceLanguage
is not null, then add it as a parameter to theForm
instance. - Post the form and get the response as a
GoogleTranslateObject
. - Return the
List
ofTranslation
instances.
@Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { Form formData = new Form(); formData.param("q", inputString); formData.param("target", targetLanguage); if(sourceLanguage != null) { formData.param("source", sourceLanguage); } WebTarget translateTarget = baseTarget.path(this.translateEndpoint); GoogleTranslateObject translateObject = translateTarget.request(MediaType.APPLICATION_JSON) .headers(super.createRapidApiHeaders()).post(Entity.form(formData), GoogleTranslateObject.class); return translateObject.getData().getTranslations(); }
Complete Class (JaxRsTutorialRestClientImpl)
The complete JaxRsTutorialRestClientImpl
class follows.
package com.example.translate.rest.api.clients; import java.util.List; import javax.annotation.PostConstruct; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Form; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import org.springframework.stereotype.Component; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.GoogleTranslateObject; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; @Component public class JaxRsTutorialRestClientImpl extends TutorialRestClient { Client client; WebTarget baseTarget; @PostConstruct private void postConstruct() { client = ClientBuilder.newClient(); baseTarget = client.target(this.endpointBase); } @Override public List<Language> getSupportedLanguages(String targetLanguage) { MultivaluedMap<String, Object> myHeaders = super.createRapidApiHeaders(); WebTarget languagesTarget = baseTarget.path(this.languagesEndpoint); GoogleTranslateObject translateObject = languagesTarget.queryParam("target", targetLanguage) .request(MediaType.APPLICATION_JSON).headers(myHeaders).get(GoogleTranslateObject.class); return translateObject.getData().getLanguages(); } @Override public List<List<Detection>> detectLanguage(String inputString) { Form formData = new Form(); formData.param("q", inputString); WebTarget detectTarget = baseTarget.path(this.detectEndpoint); GoogleTranslateObject translateObject = detectTarget.request(MediaType.APPLICATION_JSON) .headers(super.createRapidApiHeaders()).post(Entity.form(formData), GoogleTranslateObject.class); return translateObject.getData().getDetections(); } @Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { Form formData = new Form(); formData.param("q", inputString); formData.param("target", targetLanguage); if (sourceLanguage != null) { formData.param("source", sourceLanguage); } WebTarget translateTarget = baseTarget.path(this.translateEndpoint); GoogleTranslateObject translateObject = translateTarget.request(MediaType.APPLICATION_JSON) .headers(super.createRapidApiHeaders()).post(Entity.form(formData), GoogleTranslateObject.class); return translateObject.getData().getTranslations(); } private MultivaluedMap<String,Object> createRapidApiHeaders( { MultivaluedMap<String, Object> myHeaders = new MultivaluedHashMap<String, Object>(); myHeaders.add(this.rapidApiHost, this.rapidApiHostValue); myHeaders.add(this.rapidApiKey, this.rapidApiKeyValue); return myHeaders; } }
TranslateClientApplication
Now we modify TranslateClientApplication
to call our newly written class.
- Add
JaxRsTutorialRestClientImpl
as an auto-wired property. - Add code to call
jaxClient
and print the results to the console.
@Autowired JaxRsTutorialRestClientImpl jaxClient; <--- snip ----> System.out.println(jaxClient.getSupportedLanguages("en")); try { System.out.println(mapper.writeValueAsString( jaxClient.detectLanguage("This is a test. Can it translate this to english? We will see."))); } catch (JsonProcessingException e) { e.printStackTrace(); } try { System.out.println(mapper.writeValueAsString(jaxClient.translate("Hola, como estas?", "en", null))); } catch (JsonProcessingException e) { e.printStackTrace(); } try { System.out.println(mapper.writeValueAsString(jaxClient.translate("Hola, como estas?", "pt", "es"))); } catch (JsonProcessingException e) { e.printStackTrace(); }
- Build the project using Maven and execute the project from the command-line. The application prints the following results to the console.
2020-05-15 23:11:50.105 INFO 14700 --- [ main] c.e.t.r.a.t.TranslateClientApplication : Starting TranslateClientApplication on ICF2107642 with PID 14700 (C:Users42288Desktopgoogle-translate-apitutorialtutorialtargetclasses started by 42288 in C:Users42288Desktopgoogle-translate-apitutorialtutorial) 2020-05-15 23:11:50.110 INFO 14700 --- [ main] c.e.t.r.a.t.TranslateClientApplication : No active profile set, falling back to default profiles: default 2020-05-15 23:11:50.282 WARN 14700 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath 2020-05-15 23:11:50.903 INFO 14700 --- [ main] c.e.t.r.a.t.TranslateClientApplication : Started TranslateClientApplication in 1.182 seconds (JVM running for 1.588) [{ "language" : "af", "name" : "Afrikaans" }, <--- snip ---> { "language" : "he", "name" : "Hebrew" }, { "language" : "zh", "name" : "Chinese (Simplified)" }] [[{"isReliable":false,"language":"en","confidence":1}]] [{"translatedText":"Hello how are you doing?","detectedSourceLanguage":"es"}] [{"translatedText":"Oi, como vai?","detectedSourceLanguage":null}]
As you can see, developing a client using JAX-RS is straightforward. Moreover, it is the industry standard for working with RESTful web services when using Java. Because of this industry acceptance, JAX-RS is a safe bet when selecting a framework to write a REST client.
But there are situations when you might wish to use a different Java library. For instance, suppose you are writing an Android application. When using Android, a full-blown framework such as Eclipse Jersey, might prove overkill. In situations such as this, you might consider something lighter-weight such as Unirest-Java or OkHttp. And of course, as Unirest-Java and OkHttp are the two libraries used by RapidAPI as the Java examples on its website, let’s implement REST clients using these two libraries.
Unirest-Java (UnirestTutorialRestClientImpl)
Unirest-Java bills itself as a lightweight Http library for consuming RESTful APIs. Although smaller in size than a JAX-RS implementation, it remains a powerful library for writing REST clients.
- Unirest-Java 2.2.00 Java Documentation (Javadoc)
Begin by adding the unirest-java and unirest-objectmapper-jackson libraries to the pom file.
- Add the
com.konghq
dependency to the application pom file.
<dependency> <groupId>com.konghq</groupId> <artifactId>unirest-java</artifactId> <version>2.2.00</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.konghq</groupId> <artifactId>unirest-objectmapper-jackson</artifactId> <version>3.7.02</version> </dependency
- Modify
UnirestTutorialRestClientImpl
to import the Unirest-Java version ofHttpResponse
, theUnirest
class, and theJacksonObjectMapper
class.
Be careful when importing the classpath for Unirest-Java and OkHttp classes; be sure not to import a class from the wrong library.
- Add a
postConstruct
method that configures Unirest-Java to use theJacksonObjectMapper
as itsObjectMapper
. - Set the default accept header to
application/json
.
import kong.unirest.HttpResponse; import kong.unirest.Unirest; import kong.unirest.jackson.JacksonObjectMapper; @Component public class UnirestTutorialRestClientImpl extends TutorialRestClient { @PostConstruct private void postConstruct() { Unirest.config().setObjectMapper(new JacksonObjectMapper()); Unirest.config().setDefaultHeader("Accept", "application/json"); }
The Unirest
class is the entry point to the library. It is shared as a static class by all code in our class. We also statically add the JacksonObjectMapper
and the default header to the class’s configuration. We can then use the configured Unirest
class throughout the UnirestTutorialRestClientImpl
class.
Languages (getSupportedLanguages)
- Create the call to get the languages, being certain to add the needed headers. Do not forget the
accept-encoding
header, or the request will fail. - Return the
Language
object instance from the response body.
@Override public List<Language> getSupportedLanguages(String targetLanguage) { HttpResponse<GoogleTranslateObject> response = Unirest.get(this.endpointBase + this.languagesEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header("accept-encoding", "application/gzip") .header(this.rapidApiKey, this.rapidApiKeyValue).queryString("target", targetLanguage) .asObject(GoogleTranslateObject.class); return response.getBody().getData().getLanguages(); }
The Unirest-Java API adds the headers, the get URL, the query string, and then executes the request in a single statement. Recall we set Jackson as the Unirest
class’s ObjectMapper
and so we can specify the response is a GoogleTranslateObject
object instance. Unirest-Java then uses Jackson to translate the returned JSON string into the Java class. Note, had we preferred to set the headers all at once, we could have used a Map
to pass the headers to the Unirest
class.
Map<String, String> headers = new HashMap<String, String>(); headers.put(this.rapidApiHost, this.rapidApiHostValue); headers.put("accept-encoding", "application/gzip"); headers.put(this.rapidApiKey, this.rapidApiKeyValue); HttpResponse<GoogleTranslateObject> response = Unirest.get(this.endpointBase + this.languagesEndpoint).headers(headers).queryString("target", targetLanguage).asObject(GoogleTranslateObject.class);
Detect Language (detectLanguage)
- Implement the
Unirest
class’spost
method to sendinputString
as a field. - Return the nested list of
Detection
object instances.
@Override public List<List<Detection>> detectLanguage(String inputString) { HttpResponse<GoogleTranslateObject> response = Unirest.post(this.endpointBase + this.detectEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").field("q", inputString) .asObject(GoogleTranslateObject.class); return response.getBody().getData().getDetections(); }
The POST
request sets the text to detect as a form field. Because the request is an HTTP form submission, we set the header content-type
to application/x-www.form-urlencoded
. We then executed the request by calling the asObject
method. After receiving the response, we obtain the nested list from the body, and then return the nested list of Detection
object instances.
Translate Language (translate)
- Add a conditional to check if the
sourceLanguage
variable is null. - If
sourceLanguage
is not null, then add it as a field to thepost
method. - If
sourceLanguage
is null, create thepost
method without adding thesourceLanguage
as a field. - Return the list of
Translation
object instances after retrieving it from the response.
@Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { HttpResponse<GoogleTranslateObject> response = null; if (sourceLanguage != null) { response = Unirest.post(this.endpointBase + this.translateEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").field("q", inputString) .field("target", targetLanguage).field("source", sourceLanguage) .asObject(GoogleTranslateObject.class); } else { response = Unirest.post(this.endpointBase + this.translateEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").field("q", inputString) .field("target", targetLanguage).asObject(GoogleTranslateObject.class); } return response.getBody().getData().getTranslations(); }
Complete Class (UnirestTutorialRestClientImpl)
For convenience, the complete UnirestTutorialClientImpl
class is included below.
package com.example.translate.rest.api.clients; import java.util.List; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.GoogleTranslateObject; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; import kong.unirest.HttpResponse; import kong.unirest.Unirest; import kong.unirest.jackson.JacksonObjectMapper; @Component public class UnirestTutorialRestClientImpl extends TutorialRestClient { @PostConstruct private void postConstruct() { Unirest.config().setObjectMapper(new JacksonObjectMapper()); Unirest.config().setDefaultHeader("Accept", "application/json"); } @Override public List<Language> getSupportedLanguages(String targetLanguage) { HttpResponse<GoogleTranslateObject> response = Unirest.get(this.endpointBase + this.languagesEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header("accept-encoding", "application/gzip") .header(this.rapidApiKey, this.rapidApiKeyValue).queryString("target", targetLanguage) .asObject(GoogleTranslateObject.class); return response.getBody().getData().getLanguages(); } @Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { HttpResponse<GoogleTranslateObject> response = null; if (sourceLanguage != null) { response = Unirest.post(this.endpointBase + this.translateEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").field("q", inputString) .field("target", targetLanguage).field("source", sourceLanguage) .asObject(GoogleTranslateObject.class); } else { response = Unirest.post(this.endpointBase + this.translateEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").field("q", inputString) .field("target", targetLanguage).asObject(GoogleTranslateObject.class); } return response.getBody().getData().getTranslations(); } @Override public List<List<Detection>> detectLanguage(String inputString) { HttpResponse<GoogleTranslateObject> response = Unirest.post(this.endpointBase + this.detectEndpoint) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").field("q", inputString) .asObject(GoogleTranslateObject.class); return response.getBody().getData().getDetections(); } }
TranslationClientApplication
- Modify
TranslationClientApplication
to take a command-line parameter ofjaxrs
orunirest
. - Specify a
UnirestTutorialRestClientImpl
instance as an auto-wired property. - Add logic that determines the command-line property and calls either the Unirest-Java or JAX-RS client class.
if (args[0].equals("jaxrs")) { System.out.println(jaxClient.getSupportedLanguages("en")); try { System.out.println(mapper.writeValueAsString( jaxClient.detectLanguage("This is a test. Can it translate this to english? We will see."))); } catch (JsonProcessingException e) { e.printStackTrace(); } try { System.out.println(mapper.writeValueAsString(jaxClient.translate("Hola, como estas?", "en", null))); } catch (JsonProcessingException e) { e.printStackTrace(); } try { System.out.println(mapper.writeValueAsString(jaxClient.translate("Hola, como estas?", "pt", "es"))); } catch (JsonProcessingException e) { e.printStackTrace(); } } else if (args[0].equals("unirest")) { System.out.println(unirestClient.getSupportedLanguages("es")); try { System.out.println(mapper.writeValueAsString(unirestClient.translate("This is a test.", "zh", "en"))); System.out.println( mapper.writeValueAsString(unirestClient.translate("Do you speak French?", "fr", null))); } catch (JsonProcessingException e) { e.printStackTrace(); } try { System.out.println(mapper.writeValueAsString(unirestClient.detectLanguage("Nke a bụ ule."))); } catch (JsonProcessingException e) { e.printStackTrace(); } return; }
- Build the application using Maven.
- Execute the application from the command-line specifying
unirest
as the parameter.
java -jar .tutorial-0.0.1-SNAPSHOT.jar unirest
You should obtain the following results printed to the console. Full disclosure, I executed the program within Eclipse. When using Microsoft Powershell, I could not get it to print the Chinese characters. You might have a similar problem running this from the command-line.
2020-05-15 23:08:07.134 INFO 9972 --- [ main] c.e.t.r.a.t.TranslateClientApplication : Starting TranslateClientApplication on ICF2107642 with PID 9972 (C:Users42288Desktopgoogle-translate-apitutorialtutorialtargetclasses started by 42288 in C:Users42288Desktopgoogle-translate-apitutorialtutorial) 2020-05-15 23:08:07.138 INFO 9972 --- [ main] c.e.t.r.a.t.TranslateClientApplication : No active profile set, falling back to default profiles: default 2020-05-15 23:08:07.315 WARN 9972 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath 2020-05-15 23:08:07.933 INFO 9972 --- [ main] c.e.t.r.a.t.TranslateClientApplication : Started TranslateClientApplication in 1.209 seconds (JVM running for 1.634) [{ "language" : "af", "name" : "afrikáans" }, <---- snip ----> { "language" : "zh", "name" : "chino (simplificado)" }] [{"translatedText":"这是一个测试。","detectedSourceLanguage":null}] [{"translatedText":"Est-ce que tu parles français?","detectedSourceLanguage":"en"}] [[{"isReliable":false,"language":"ig","confidence":1}]]
OkHttp (OkHttpTutorialRestClientImpl)
Let’s finish the tutorial by implementing the OkHttpTutorialRestClientImpl
class using the OkHttp library.
- The OkHttp documentation (okhttp).
As with the Unirest-Java library, we must first add the library to our application’s pom file.
- Add the latest OkHttp version to the application pom file.
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.6.0</version> </dependency>
- Import the
Okhttp
FormBody
,OkHttpClient
,Request
,RequestBody
, andResponse
classes. - Import Jackson’s
ObjectMapper
. - Create a
postConstruct
method and instantiate anObjectMapper
instance.
import okhttp3.FormBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import com.fasterxml.jackson.databind.ObjectMapper; @Component public class OkHttpTutorialRestClientImpl extends TutorialRestClient { ObjectMapper objectMapper; @PostConstruct private void postConstruct() { this.objectMapper = new ObjectMapper(); }
Languages (getSupportedLanguages)
- Create a new
OkHttpClient
instance. - Build the
Request
instance. - Use the
Client
instance to execute the request. - Get the
GoogleTranslateObject
from the response and return the list ofLanguage
instances.
@Override public List<Language> getSupportedLanguages(String targetLanguage) { try { OkHttpClient client = new OkHttpClient(); String url = this.endpointBase + this.languagesEndpoint + "?target=" + targetLanguage; Request request = new Request.Builder().url(url).get().addHeader(this.rapidApiHost, this.rapidApiHostValue) .addHeader(this.rapidApiKey, this.rapidApiKeyValue).addHeader("accept-encoding", "application/gzip") .build(); Response response = client.newCall(request).execute(); GoogleTranslateObject googleTranslateObject = objectMapper.readValue(response.body().string(), GoogleTranslateObject.class); return googleTranslateObject.getData().getLanguages(); } catch (IOException e) { e.printStackTrace(); return null; } }
The OkHttpClient
takes a Request
instance, calls the newCall
method to create a request and then executes that request using the execute
method. It then uses the Jackson ObjectMapper
instance to translate the response body to a GoogleTranslateObject
instance and then obtains and returns the list of Language
instances.
Detect Language (detectLanguage)
- Create a new form submission using the
FormBody
class. - Execute the post request and return the nested list of
Detection
instances from the response.
@Override public List<List<Detection>> detectLanguage(String inputString) { try { OkHttpClient client = new OkHttpClient(); String url = this.endpointBase + this.detectEndpoint; RequestBody formBody = null; formBody = new FormBody.Builder().add("q", inputString).build(); Request request = new Request.Builder().url(url).post(formBody) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").build(); Response response = client.newCall(request).execute(); GoogleTranslateObject googleTranslateObject = objectMapper.readValue(response.body().string(), GoogleTranslateObject.class); return googleTranslateObject.getData().getDetections(); } catch (IOException e) { e.printStackTrace(); return null; } }
We start by creating an OkHttpClient
instance and a RequestBody
instance. We pass the inputString
as a field to the RequestBody
instance and then pass that to a Request
instance. Finally, we call the OkHttpClient
instance’s newCall
method, passing the Request
instance as a parameter. We then return the nested list of Detection
instances.
Translate
- Create a conditional that checks if the
sourceLanguage
is null. - Build the request with the
sourceLanguage
as a form field if not null. - Submit the post request and return the list of
Translation
instances.
@Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { try { OkHttpClient client = new OkHttpClient(); String url = this.endpointBase + this.translateEndpoint; RequestBody formBody = null; if (sourceLanguage != null) { formBody = new FormBody.Builder().add("q", inputString).add("target", targetLanguage) .add("source", sourceLanguage).build(); } else { formBody = new FormBody.Builder().add("q", inputString).add("target", targetLanguage).build(); } Request request = new Request.Builder().url(url).post(formBody) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").build(); Response response = client.newCall(request).execute(); GoogleTranslateObject googleTranslateObject = objectMapper.readValue(response.body().string(), GoogleTranslateObject.class); return googleTranslateObject.getData().getTranslations(); } catch (IOException e) { e.printStackTrace(); return null; } }
Complete Class (OkHttpTutorialRestClientImpl)
The complete OkHttpTutorialRestClientImpl
class follows.
package com.example.translate.rest.api.clients; import java.io.IOException; import java.util.List; import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; import com.example.translate.rest.api.json.object.Detection; import com.example.translate.rest.api.json.object.GoogleTranslateObject; import com.example.translate.rest.api.json.object.Language; import com.example.translate.rest.api.json.object.Translation; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.FormBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; @Component public class OkHttpTutorialRestClientImpl extends TutorialRestClient { ObjectMapper objectMapper; @PostConstruct private void postConstruct() { this.objectMapper = new ObjectMapper(); } @Override public List<Language> getSupportedLanguages(String targetLanguage) { try { OkHttpClient client = new OkHttpClient(); String url = this.endpointBase + this.languagesEndpoint + "?target=" + targetLanguage; Request request = new Request.Builder().url(url).get().addHeader(this.rapidApiHost, this.rapidApiHostValue) .addHeader(this.rapidApiKey, this.rapidApiKeyValue).addHeader("accept-encoding", "application/gzip") .build(); Response response = client.newCall(request).execute(); GoogleTranslateObject googleTranslateObject = objectMapper.readValue(response.body().string(), GoogleTranslateObject.class); return googleTranslateObject.getData().getLanguages(); } catch (IOException e) { e.printStackTrace(); return null; } } @Override public List<List<Detection>> detectLanguage(String inputString) { try { OkHttpClient client = new OkHttpClient(); String url = this.endpointBase + this.detectEndpoint; RequestBody formBody = null; formBody = new FormBody.Builder().add("q", inputString).build(); Request request = new Request.Builder().url(url).post(formBody) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").build(); Response response = client.newCall(request).execute(); GoogleTranslateObject googleTranslateObject = objectMapper.readValue(response.body().string(), GoogleTranslateObject.class); return googleTranslateObject.getData().getDetections(); } catch (IOException e) { e.printStackTrace(); return null; } } @Override public List<Translation> translate(String inputString, String targetLanguage, String sourceLanguage) { try { OkHttpClient client = new OkHttpClient(); String url = this.endpointBase + this.translateEndpoint; RequestBody formBody = null; if (sourceLanguage != null) { formBody = new FormBody.Builder().add("q", inputString).add("target", targetLanguage) .add("source", sourceLanguage).build(); } else { formBody = new FormBody.Builder().add("q", inputString).add("target", targetLanguage).build(); } Request request = new Request.Builder().url(url).post(formBody) .header(this.rapidApiHost, this.rapidApiHostValue).header(this.rapidApiKey, this.rapidApiKeyValue) .header("accept-encoding", "application/gzip") .header("content-type", "application/x-www-form-urlencoded").build(); Response response = client.newCall(request).execute(); GoogleTranslateObject googleTranslateObject = objectMapper.readValue(response.body().string(), GoogleTranslateObject.class); return googleTranslateObject.getData().getTranslations(); } catch (IOException e) { e.printStackTrace(); return null; } } }
TranslateClientApplication
- Modify
TranslateClientApplication
to auto-wire anOkHttpRestClientImpl
. - Add a third conditional statement that checks if the command-line parameter passed to the application is
okhttp
.
else if (args[0].equals("okhttp")) { System.out.println(okHttpClient.getSupportedLanguages("fr")); try { System.out.println(mapper.writeValueAsString( okHttpClient.translate("Sinto muita falta de você, minha princesa.", "fr", "pt"))); System.out.println(mapper.writeValueAsString( okHttpClient.translate("Hello how are you, let's translate to French.", "zh", null))); } catch (JsonProcessingException e) { e.printStackTrace(); } try { System.out.println(mapper .writeValueAsString(okHttpClient.detectLanguage("Sinto muita falta de você, minha princesa."))); } catch (JsonProcessingException e) { e.printStackTrace(); } return; }
- Build the application using Maven.
- Run the application from the command-line specifying
okhttp
as the parameter.
2020-05-15 23:01:37.254 INFO 1432 --- [ main] c.e.t.r.a.t.TranslateClientApplication : Starting TranslateClientApplication on ICF2107642 with PID 1432 (C:Users42288Desktopgoogle-translate-apitutorialtutorialtargetclasses started by 42288 in C:Users42288Desktopgoogle-translate-apitutorialtutorial) 2020-05-15 23:01:37.262 INFO 1432 --- [ main] c.e.t.r.a.t.TranslateClientApplication : No active profile set, falling back to default profiles: default 2020-05-15 23:01:37.458 WARN 1432 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath 2020-05-15 23:01:38.050 INFO 1432 --- [ main] c.e.t.r.a.t.TranslateClientApplication : Started TranslateClientApplication in 1.197 seconds (JVM running for 1.615) [{ "language" : "af", "name" : "Afrikaans" }, < ---- snip ----> { "language" : "zh", "name" : "Chinois (simplifié)" }] [{"translatedText":"Tu me manques tellement, ma princesse.","detectedSourceLanguage":null}] [{"translatedText":"您好,让我们翻译成法语。","detectedSourceLanguage":"en"}] [[{"isReliable":false,"language":"pt","confidence":1}]]
Summary
In this tutorial, we used three different Java REST client libraries to call the three Google Translate REST endpoints. We first used the Eclipse Maven implementation of the JAX-RS standard. We then used the Unirest-Java and OkHttp lightweight REST client libraries. Before developing the application, we evaluated the Google Translate API’s REST endpoints and then modeled our application by creating a use-case diagram, class diagram, and object model diagram. We used the use-case diagram to create an outline of the tasks our application was to perform. We then used the models and outlines to develop our application. We implemented REST clients using JAX-RS, Unirest-Java, and OkHttp. Each client called all three of the Google Translate REST endpoints.
We covered a lot of material in this tutorial. Not only did we explore the three endpoints, we also explored how to write REST client, from analyzing the endpoints to modeling our application calling those endpoints, and then implementing the code. Hopefully you are convinced that preliminarily modeling even a simple REST client is not wasted effort. I truly believe it helps to understand immensely. You now have a better understanding of how to incorporate the Google Translate API on RapidAPI into your Java applications.
Leave a Reply