Setting up a maven openai compatible java client takes less than ten minutes if you know which dependencies to pull and how to point them at a compliant endpoint. This guide walks through a minimal Maven project that sends a chat completion request to any OpenAI-compatible API, from scaffold to verified response.
Step 1: Scaffold the Maven project
Start from an empty directory and generate the standard layout. The Quickstart archetype gives you src/main/java and a bare pom.xml.
mvn archetype:generate -DgroupId=com.example.llm \
-DartifactId=openai-client -DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
cd openai-client
Delete the generated App.java if you want a clean slate, or just replace its contents later. The goal is a runnable maven openai compatible java client that compiles with mvn compile and runs without extra classpath wrestling.
Step 2: Declare dependencies in pom.xml
You do not need a heavy SDK. Java 11+ ships java.net.http.HttpClient, which handles HTTP/2, timeouts, and request bodies cleanly. The only external artifact you must add is Jackson for JSON binding—the OpenAI-compatible request and response shapes are simple enough to map with ObjectNode.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.llm</groupId>
<artifactId>openai-client</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
Pin the compiler to Java 17. Most CI images have it, and HttpClient is stable there. Run mvn dependency:tree to confirm Jackson resolved; you should see jackson-databind and its jackson-core/jackson-annotations transitive pulls.
Step 3: Externalize the endpoint and API key
Never hardcode credentials. The OpenAI-compatible contract uses two env vars: OPENAI_API_BASE (the URL up to /v1) and OPENAI_API_KEY. Export them in your shell or set them in your IDE run configuration.
export OPENAI_API_BASE="https://api.openai.com/v1"
export OPENAI_API_KEY="sk-your-key-here"
If you route through a gateway such as n4n.ai, the base URL is a single OpenAI-compatible endpoint that covers 240+ models and handles automatic fallback when a provider is degraded. The same env var pattern works—just point OPENAI_API_BASE at the gateway URL and keep your key.
Step 4: Write the chat completion call
Create src/main/java/com/example/llm/ChatClient.java. This class is the core of your maven openai compatible java client. It builds a minimal chat.completions payload, sends it, and prints the assistant message plus usage.
package com.example.llm;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class ChatClient {
public static void main(String[] args) throws Exception {
String base = System.getenv("OPENAI_API_BASE");
String key = System.getenv("OPENAI_API_KEY");
if (base == null || key == null) {
throw new IllegalStateException("Set OPENAI_API_BASE and OPENAI_API_KEY");
}
ObjectMapper mapper = new ObjectMapper();
ObjectNode payload = mapper.createObjectNode();
payload.put("model", "gpt-4o-mini");
ArrayNode messages = payload.putArray("messages");
ObjectNode msg = messages.addObject();
msg.put("role", "user");
msg.put("content", "Explain Maven in one sentence.");
payload.put("temperature", 0.7);
String body = mapper.writeValueAsString(payload);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(base + "/chat/completions"))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.err.println("Request failed: " + response.statusCode());
System.err.println(response.body());
System.exit(1);
}
JsonNode root = mapper.readTree(response.body());
String content = root.path("choices").get(0)
.path("message").path("content").asText();
System.out.println("Model reply: " + content);
System.out.println("Usage: " + root.path("usage").toString());
}
}
A few notes from production use: reuse a single HttpClient instance across calls—it pools connections. Set an explicit connectTimeout; the default is infinite. The model field is a string; any OpenAI-compatible server will reject unknown models with a 4xx, so log the body on failure.
Step 5: Compile and run
Build the project, then execute the class via the exec plugin.
mvn compile
mvn exec:java -Dexec.mainClass="com.example.llm.ChatClient"
If you prefer a fat jar, add maven-shade-plugin and run java -jar target/openai-client.jar. Either path proves the maven openai compatible java client is wired correctly.
Step 6: Verify success
A successful run prints the model’s text and a usage block. Example console output:
Model reply: Maven is a build automation and dependency management tool for Java projects.
Usage: {"prompt_tokens":12,"completion_tokens":9,"total_tokens":21}
The HTTP response body conforms to the OpenAI schema:
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Maven is a build automation and dependency management tool for Java projects."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 9,
"total_tokens": 21
}
}
If you see a 401, your key is missing or malformed. A 429 means rate limiting—back off and retry. A 404 on /chat/completions means OPENAI_API_BASE points at the wrong path (it should not include /v1 in the env var; the code appends it). You now have a working maven openai compatible java client that talks to any compliant backend.
Why skip the full SDK
The OpenAI Java wrappers built on Retrofit hide the HttpClient and make custom base URLs awkward—some require reflection or builder hacks to override the endpoint. For a maven openai compatible java client, the standard library is transparent: you see the exact JSON, headers, and status codes. When you later need streaming or function calling, you extend this same request loop rather than fighting a wrapper’s abstractions.
Handling routing and fallback
If your OPENAI_API_BASE points at a gateway, you can send a routing hint via the headers map or provider-specific extensions; compliant gateways forward cache-control and routing directives. Because the gateway consolidates 240+ models behind one URL, your client code stays identical when you switch from gpt-4o-mini to a different model or provider. The usage field still returns per-token metering, which you can ship to your own analytics.
Next steps
Add a retry-with-backoff loop around client.send for 429/5xx. Extract the payload construction into a method that accepts List<Message> so you can hold conversation state. If you need SSE streaming, swap BodyHandlers.ofString() for a line-oriented subscriber and parse data: frames—the endpoint contract is unchanged. The setup above is the smallest correct surface; everything else is application logic.