Build a Java application to talk to ChatGPT

Build your own Java-based chatbot and get a feel for interacting with the ChatGPT API in a Java client.

AI-human interface.
PopTika/Shutterstock

ChatGPT is a fun and useful large language model (LLM) conversational AI that, given its renown, doesn’t need much introduction. Let’s see how we can interact with the ChatGPT API service from a simple Java program.

Get started with ChatGPT

The first thing you'll need to do is to obtain an API key. You can get access to one with the free version of ChatGPT available from OpenAI. Once you're signed up, you’ll have access to the 3.5 version at no cost. (Version 4 still requires a subscription fee at the moment.) 

Once you have an account and you are signed in, you can generate a new API key. Save it somewhere safe; we’ll use it later on.

Create a new Java project

Now let’s start creating a new Java application for this demo. We’ll use Maven from the command line. You can lay out a new project using the code in Listing 1.

Listing 1. Generate a new project


mvn archetype:generate -DgroupId=com.infoworld -DartifactId=java-gpt -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false -DarchetypeVersion=1.4

That will scaffold a new project in the /java-gpt directory. Before we continue, navigate to the /java-gpt/pom.xml file and set the Java compiler and source version to 11.

You can test this out with the command in Listing 2, which currently outputs “Hello, World!” to the console. From here on, we’ll use this command to run the application.

Listing 2. Test run the new application


mvn clean compile exec:java -Dexec.mainClass=com.infoworld.App

Set up the ChatGPT application

Let’s define exactly what our ChatGPT application will do. We’ll keep things very basic for this demo. We want to accept an input string from the user and then output the AI’s response to it on the console.

To begin, let’s frame a simple App.java main class that accepts input. We'll define the URL and API key, which the main class will hand off to a ChatBot class to do the real work. You can see the App.java main class in Listing 3.

Listing 3. App.java main class


package com.infoworld;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
  public static void main(String[] args) {
    // Set ChatGPT endpoint and API key
    String endpoint = "https://api.openai.com/v1/chat/completions";
    String apiKey = "<YOUR-API-KEY>";
        
    // Prompt user for input string
    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
      System.out.print("Enter your message: ");
      String input = reader.readLine();
            
      // Send input to ChatGPT API and display response
      String response = ChatBot.sendQuery(input, endpoint, apiKey);
      LOGGER.info("Response: {}", response);
    } catch (IOException e) {
      LOGGER.error("Error reading input: {}", e.getMessage());
    } catch (JSONException e) {
      LOGGER.error("Error parsing API response: {}", e.getMessage());
    } catch (Exception e) {
      LOGGER.error("Unexpected error: {}", e.getMessage());
    }
  }
}

This class is fairly simple: It reads a line from the user with reader.readLine(), then uses it to call the ChatBot.sendQuery() static method. I'll show you ChatBot in a moment. For now, make sure you set the <YOUR-API-KEY> token to that OpenAI ChatGPT token we saved at the outset of this article. Also note that OpenAI has been making changes to the OpenAI API, so the endpoint URL of https://api.openai.com/v1/chat/completions is correct currently, but it may be different from earlier (even recent) documentation. If you get any errors, make sure the endpoint URL is is up-to-date with the latest iteration of the OpenAI API.

The ChatBot class

Notice, also, that we have configured a simple logger to watch what’s happening and track any errors. We’ll need to add some dependencies to Maven to support this class, but first, let’s look at the ChatBot class, shown in Listing 4. For convenience, we’re just putting them both in the same package scope.

Listing 4. A simple ChatBot


package com.infoworld;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

public class ChatBot {
  private static final Logger LOGGER = LoggerFactory.getLogger(ChatBot.class);

  public static String sendQuery(String input, String endpoint, String apiKey) {
    // Build input and API key params
    JSONObject payload = new JSONObject();
    JSONObject message = new JSONObject();
    JSONArray messageList = new JSONArray();

    message.put("role", "user");
    message.put("content", input);
    messageList.put(message);

    payload.put("model", "gpt-3.5-turbo"); // model is important
    payload.put("messages", messageList);
    payload.put("temperature", 0.7);

    StringEntity inputEntity = new StringEntity(payload.toString(), ContentType.APPLICATION_JSON);

    // Build POST request
    HttpPost post = new HttpPost(endpoint);
    post.setEntity(inputEntity);
    post.setHeader("Authorization", "Bearer " + apiKey);
    post.setHeader("Content-Type", "application/json");

    // Send POST request and parse response
    try (CloseableHttpClient httpClient = HttpClients.createDefault();
      CloseableHttpResponse response = httpClient.execute(post)) {
        HttpEntity resEntity = response.getEntity();
        String resJsonString = new String(resEntity.getContent().readAllBytes(), StandardCharsets.UTF_8);
        JSONObject resJson = new JSONObject(resJsonString);

        if (resJson.has("error")) {
          String errorMsg = resJson.getString("error");
          LOGGER.error("Chatbot API error: {}", errorMsg);
          return "Error: " + errorMsg;
        }

        // Parse JSON response
        JSONArray responseArray = resJson.getJSONArray("choices");
        List<String> responseList = new ArrayList<>();

        for (int i = 0; i < responseArray.length(); i++) {
          JSONObject responseObj = responseArray.getJSONObject(i);
          String responseString = responseObj.getJSONObject("message").getString("content");
          responseList.add(responseString);
        }

        // Convert response list to JSON and return it
        Gson gson = new Gson();
        String jsonResponse = gson.toJson(responseList);
        return jsonResponse;
      } catch (IOException | JSONException e) {
        LOGGER.error("Error sending request: {}", e.getMessage());
        return "Error: " + e.getMessage();
      }
  }
}

ChatBot looks like it has a lot going on, but it's fairly simple. The biggest issue is dealing with JSON. First, we have to marshal the string input passed to sendQuery() into the JSON format that ChatGPT expects (again, there has been some churn on this lately). ChatGPT allows for an ongoing conversation, so it looks for a collection of messages, tagged with the role of either user or assistant. The AI looks at these as the back-and-forth responses in the chat. (There is also a system role, which is used to set the “tone” of the whole conversation, although this property is inconsistently applied.) The JSON format that ChatGPT expects looks like Listing 5 (taken from the OpenAI docs).

Listing 5. Sample ChatGPT submit format JSON


messages:[
  {"role": "system", "content": "You are a helpful assistant."},
  {"role": "user", "content": "Who won the world series in 2020?"},
  {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
  {"role": "user", "content": "Where was it played?"}
]

Set up the JSON response

So, in our case, we are just using a simple set of messages, with a single role:user and content set to the input string. We also set the model and temperature fields. The model field is important in defining which version of ChatGPT is being used. There are several options, including the newer (for pay) GPT-4. Temperature is a GPT feature that tells the AI how “creative” to be (in terms of avoiding word reuse).

With the proper JSON in hand, we use the Apache HTTP library to build up a POST request, and set the body to the JSON (via post.setEntity()). Notice that we set that API key on the auth header with this line:


post.setHeader("Authorization", "Bearer " + apiKey);

Next, we use a try-with-resource block to issue the request. With the response done (assuming we avoided any errors) we just have to navigate the JSON response structure and return the most recent message:content.

Now we’re almost ready to test it out. First, add the necessary dependencies to the pom.xml, as shown in Listing 6.

Listing 6. Maven dependencies in pom.xml


<dependencies>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
   </dependency>
   <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.7</version>
   </dependency>
   <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.6</version>
   </dependency>
   <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.13</version>
   </dependency>
</dependencies>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.30</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

Now when you run the code, you should see it working. It will look something like Listing 7.

Listing 7. Talking to ChatGPT with the Java command-line application


$ mvn clean compile exec:java -Dexec.mainClass=com.infoworld.App

Enter your message: This is the forest primeval.

22:43:35.384 [com.infoworld.App.main()] INFO com.infoworld.App - Response: "The murmuring pines and the hemlocks,\n\nBearded with moss, and in garments green,\n\nIndistinct in the twilight,\n\nStand like Druids of eld,\n\nWith voices sad and prophetic,\n\nStand like harpers hoar,\n\nWith beards that rest on their bosoms.\n\nLoud from its rocky caverns,\n\nThe deep-voiced neighboring ocean\n\nSpeaks, and in accents disconsolate\n\nAnswers the wail of the forest."

See my java-chatgpt GitHub repo for the complete application code.

Conclusion

As this article demonstrates, it's fairly easy to build a ChatGPT client in Java. The JSON handling is a bit cumbersome, but otherwise, it’s easy to see how we could readily expand the demo code show here. As a first exercise, consider extending the ChatGPT program into a REPL that allows for an ongoing conversation.

Copyright © 2023 IDG Communications, Inc.