Most Java teams calling OpenAI use the community openai-java library or the official SDK against api.openai.com. To switch OpenAI Java SDK to n4n, you do not rewrite your prompt logic—you repoint the client at n4n.ai’s OpenAI-compatible endpoint, remap model strings, and optionally pass routing headers. This post walks through a full migration you can complete in one sitting.
Step 1: Inventory your existing OpenAI Java SDK calls
Find every place you construct the client and every hardcoded model name. A typical service class looks like this:
import com.theokanning.openai.service.OpenAiService;
import com.theokanning.openai.completion.chat.*;
public class Assistant {
private final OpenAiService service = new OpenAiService(System.getenv("OPENAI_KEY"));
public String ask(String prompt) {
ChatCompletionRequest req = ChatCompletionRequest.builder()
.model("gpt-4o")
.messages(List.of(new ChatMessage("user", prompt)))
.temperature(0.2)
.build();
return service.createChatCompletion(req)
.getChoices().get(0).getMessage().getContent();
}
}
Note three things: the API key source, the model string, and the request shape. None of the request fields change when you switch OpenAI Java SDK to n4n—the gateway speaks the same Chat Completions contract.
Step 2: Externalize the base URL and API key
Stop hardcoding the OpenAI host. Pull both values from environment or config:
export N4N_BASE_URL="https://api.n4n.ai/v1"
export N4N_API_KEY="sk-n4n-your-real-key"
If you still want to keep the OpenAI key for fallback during testing, name it separately. But for a clean cutover, the only credential your code needs is the n4n key.
Step 3: Rebuild the client against n4n’s endpoint
The openai-java library lets you build an OpenAiApi with a custom base URL. That is the entire switch OpenAI Java SDK to n4n core change:
import com.theokanning.openai.OpenAiApi;
import com.theokanning.openai.service.OpenAiService;
OpenAiApi api = OpenAiApi.builder()
.baseUrl(System.getenv("N4N_BASE_URL"))
.apiKey(System.getenv("N4N_API_KEY"))
.build();
OpenAiService service = new OpenAiService(api);
Everything else—createChatCompletion, ChatMessage, streaming APIs—works unchanged. n4n.ai exposes a single OpenAI-compatible endpoint covering 240+ models, so you lose nothing in surface area.
If you use the official OpenAI Java SDK (com.openai.client), the equivalent is:
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
OpenAIClient client = OpenAIOkHttpClient.builder()
.baseUrl(System.getenv("N4N_BASE_URL"))
.apiKey(System.getenv("N4N_API_KEY"))
.build();
Step 4: Map model identifiers
OpenAI model names like gpt-4o work if n4n routes them to the OpenAI provider. But the point of a gateway is model choice. Use qualified names to pin a provider or use a fallback chain:
ChatCompletionRequest req = ChatCompletionRequest.builder()
.model("anthropic/claude-3-5-sonnet")
// or "openai/gpt-4o" to be explicit
.messages(List.of(new ChatMessage("user", prompt)))
.build();
n4n forwards the model field verbatim to its routing layer. If you omit the provider prefix, the gateway applies its default routing. Keep a single enum or config map for model strings so you can change them without hunting through services.
Step 5: Pass routing and cache-control headers
The SDKs above do not expose arbitrary headers cleanly. If you need client routing directives or provider cache-control hints, drop to a plain java.net.http.HttpClient for those calls. n4n honors routing headers and forwards cache-control to the upstream provider.
import java.net.http.*;
import java.net.URI;
String body = """
{
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": "Summarize this"}],
"temperature": 0.1
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("N4N_BASE_URL") + "/chat/completions"))
.header("Authorization", "Bearer " + System.getenv("N4N_API_KEY"))
.header("X-N4N-Route", "openai;fallback=anthropic") // client routing directive
.header("Cache-Control", "max-age=3600") // forwarded to provider
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> resp = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
Automatic fallback when a provider is rate-limited or degraded is built in, but explicit routing headers let you constrain behavior per request. Per-token usage metering is returned in the standard usage field of the JSON response, so your existing billing hooks keep working.
Step 6: Verify the migration with a smoke test
Write a tiny main or JUnit test that sends one request and asserts on the response shape. Do not trust a 200 alone—check usage and model in the payload.
public class SmokeTest {
public static void main(String[] args) {
OpenAiApi api = OpenAiApi.builder()
.baseUrl(System.getenv("N4N_BASE_URL"))
.apiKey(System.getenv("N4N_API_KEY"))
.build();
OpenAiService svc = new OpenAiService(api);
ChatCompletionRequest req = ChatCompletionRequest.builder()
.model("openai/gpt-4o-mini")
.messages(List.of(new ChatMessage("user", "ping")))
.build();
var resp = svc.createChatCompletion(req);
System.out.println(resp.getChoices().get(0).getMessage().getContent());
System.out.println("Tokens: " + resp.getUsage().getTotalTokens());
}
}
Run it:
mvn -q compile exec:java -Dexec.mainClass=SmokeTest
Success criteria: you get a non-empty content string, usage.total_tokens is greater than zero, and the latency is within your timeout. If you see 401, your key is wrong. If you see 404 on the model, your model string is not routed.
Step 7: Optional – drop the SDK for a thin HTTP client
Once you switch OpenAI Java SDK to n4n, you may decide the SDK is dead weight. The gateway is just a REST endpoint. A minimal wrapper removes a dependency and gives you full header control:
public record ChatResp(String content, int totalTokens) {}
public class N4nClient {
private final String base = System.getenv("N4N_BASE_URL");
private final String key = System.getenv("N4N_API_KEY");
private final HttpClient http = HttpClient.newHttpClient();
public ChatResp chat(String model, String prompt) throws Exception {
String json = """
{"model":"%s","messages":[{"role":"user","content":"%s"}]}
""".formatted(model, prompt);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(base + "/chat/completions"))
.header("Authorization", "Bearer " + key)
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> r = http.send(req, HttpResponse.BodyHandlers.ofString());
// parse with Jackson or Gson; extract content and usage.total_tokens
return parse(r.body());
}
}
This is roughly 40 lines including JSON parsing, versus pulling in a SDK that masks the wire format. For services that only do chat completions, the thin client is easier to audit.
Troubleshooting and gotchas
Streaming
If you used stream=true with the OpenAI SDK, the SSE format is identical. With the thin client, read the response body as a stream and split on data: lines. n4n does not alter the event shape.
Timeouts
The default OpenAiService timeout is 10 seconds. Gateway fan-out to a fallback provider can add latency. Set an explicit timeout:
OpenAiService service = new OpenAiService(api, Duration.ofSeconds(60));
Model availability
A model that exists on OpenAI may not be enabled on the gateway by default if the provider is not configured for your account. Qualify the name (openai/gpt-4o) to get a clear error instead of silent rerouting.
Cache control
If you send Cache-Control headers, confirm the upstream provider supports caching. n4n forwards the hint; it does not invent cache behavior.
Key scoping
Treat the n4n key like any prod secret. Per-token metering means a leaked key shows up as usage immediately, not as a flat subscription charge.
The switch OpenAI Java SDK to n4n is fundamentally a base-URL and model-string change. The harder part is deciding which of the 240+ models you actually want, and whether to keep the SDK or own the HTTP layer. Do the smoke test first, then optimize.