LangChain4j: connect a Java app to Claude and GPT via a gateway
LangChain4j works with any OpenAI-compatible address. Build setup, chat, streaming and an AI Service with @Tool on the Zukko gateway.
4 min · · development · models
When people say "LangChain Java" they usually mean the LangChain4j library: a separate project for the JVM with its own API, not a port of the Python code. It has a module for OpenAI-compatible servers, and that module lets you point an application at a gateway and call Claude, GPT, DeepSeek and other models without changing code. Below is setup for Maven and Spring Boot, a streaming reply and a service with tools.
LangChain Java: what we connect and why
In the LangChain4j documentation, the langchain4j-open-ai module is described as its own implementation of the OpenAI REST client, working best with Spring and Quarkus. The builder has baseUrl, apiKey and modelName parameters, and those are exactly what we need: Zukko accepts Chat Completions requests at https://api.zukko.pro/v1, so you only supply your address, a fetch_... key and a model name from the catalog.
The upside is that the model is just a string. You can switch from claude-sonnet-5-5 to gpt-6.1-sol or glm-5.3-flash without recompiling if you move the name into settings.
Dependency
For a plain Java project:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.22.0</version>
</dependency>Version 1.22.0 is what the documentation listed when this article was written. Check the current version at docs.langchain4j.dev, because the library updates often. Put the key in an environment variable:
export ZUKKO_API_KEY="fetch_your_key"The balance tops up from $1: SBP (the payment page adds 8.5% on top), cryptocurrency, MasterCard cards.
A simple chat
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
public class Main {
public static void main(String[] args) {
ChatModel model = OpenAiChatModel.builder()
.baseUrl("https://api.zukko.pro/v1")
.apiKey(System.getenv("ZUKKO_API_KEY"))
.modelName("claude-sonnet-5-5")
.temperature(0.0)
.build();
String answer = model.chat("How does a record differ from a regular class in Java?");
System.out.println(answer);
}
}If an answer comes back, the connection works. After that everything is standard LangChain4j: messages, memory, templates. Request parameters are described in the Chat Completions reference.
Spring Boot
In Spring Boot you configure application.properties instead of the builder. The property names are taken from the module documentation:
langchain4j.open-ai.chat-model.base-url=https://api.zukko.pro/v1
langchain4j.open-ai.chat-model.api-key=${ZUKKO_API_KEY}
langchain4j.open-ai.chat-model.model-name=claude-sonnet-5-5This uses the langchain4j-open-ai-spring-boot-starter. That is the Spring Boot 3 option; for Spring Boot 4 the documentation lists a separate starter, langchain4j-open-ai-spring-boot4-starter, so pick by your Spring version. After that a ChatModel bean is created and can be injected into controllers and services. If you work with Spring AI rather than LangChain4j, see the note on Spring AI.
Streaming
For streaming output there is a separate class, OpenAiStreamingChatModel. With AI Services it is convenient to use with a TokenStream return type:
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
interface Assistant {
TokenStream chat(String message);
}
var streamingModel = OpenAiStreamingChatModel.builder()
.baseUrl("https://api.zukko.pro/v1")
.apiKey(System.getenv("ZUKKO_API_KEY"))
.modelName("gpt-6.1-sol")
.build();
Assistant assistant = AiServices.create(Assistant.class, streamingModel);
assistant.chat("Explain how the G1 garbage collector works")
.onPartialResponse(System.out::print)
.onCompleteResponse(r -> System.out.println("\n[done]"))
.onError(Throwable::printStackTrace)
.start();start() launches the stream without blocking the calling thread, so in a console program you need to wait for completion, for example with a CompletableFuture (the LangChain4j AI Services documentation has a waiting example).
A service with tools
The main feature of AI Services: ordinary Java methods become tools the model can call. An @Tool annotation is enough.
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
class OrderTools {
@Tool("Returns the status of an order by its number")
String orderStatus(@P("Order number") String orderId) {
return "Order " + orderId + ": handed over to delivery";
}
}
interface SupportBot {
String chat(String message);The library builds the function description from the annotation and parameter types, calls the method when the model requests the tool, and returns the result to the model. It is the same loop as working with the API by hand, just without manual message packing. Tool calling support depends on the specific model, so test your choice on your scenarios.
Which model to pick for Java
There is no universal answer, so compare on price, context and strength index (Intelligence Index, Artificial Analysis), then test on your own code. Prices as of October 18, 2026, $ per 1M tokens:
| Model | Input / output | Strength index | Context |
|---|---|---|---|
claude-opus-5-5 | 0.8 / 4 | 58 | 1M |
gpt-6.1-sol | 0.27 / 1.32 | 52 | 1M |
claude-sonnet-5-5 | 0.4 / 2 | 48 | 1M |
glm-5.3-flash | 0.04 / 0.04 | 42 | 128K |
A worked example for gpt-6.1-sol: 1,000 requests, each with 1,500 input tokens and 400 output tokens.
- input: 1,500,000 × 0.27 / 1,000,000 = $0.405;
- output: 400,000 × 1.32 / 1,000,000 = $0.528;
- total $0.933.
For glm-5.3-flash: 1,500,000 × 0.04 / 1,000,000 = $0.06 plus 400,000 × 0.04 / 1,000,000 = $0.016, total $0.076. The current list and prices come from GET https://api.zukko.pro/v1/models.
A caveat on caching: for Claude, the gateway sees the cache discount only in the Anthropic format, while LangChain4j with this module sends requests in the OpenAI format, so all input is billed at the input price. For GPT models, cache reads are counted in this format too.
FAQ
Is the /v1 suffix needed in baseUrl?
Yes. The LangChain4j OpenAI module is an OpenAI-compatible client, so use https://api.zukko.pro/v1. Without /v1 the path is built wrongly and you get a 404.
Can I call Claude, not just GPT?
Yes, claude-sonnet-5-5 goes into modelName like any other. The gateway accepts any catalog model in either format.
How do I tell that a key is restricted by model?
A 403 with the code model_not_allowed_for_key means the key's allowed-model list does not contain yours. Change the list in the dashboard or use another model. An unknown name gives a 404 model_not_found.
Does this work from Russia?
You can top up the balance from Russia by SBP, MasterCard or crypto. Your application code does not change.
To get started, create a key in the dashboard, put it in ZUKKO_API_KEY and run the simple chat example.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Prompt Engineering Examples: 12 Ready-Made Templates for WorkTwelve prompt templates for everyday tasks: what each one says, why it is built that way, and what a single API call costs.
- A database for RAG: where to store knowledge — vector DB, search or contextRAG does not require a vector database. Four ways to store knowledge, the cost of a query for each, and a working SQLite example.
- The best AI for C# programming: choosing a model and setting up an agentWe give no ranking of the best model for C#, but we show how to compare options by price and index, set up an agent for .NET and verify the result with builds and tests.
- Claude Opus 4.6: what it is and how to move to Opus 5.5 safelyOpus 4.6, 4.7 and 4.8 are the previous generation, and the catalogue now has Opus 5 and 5.5. A four-step migration plan: model id, your own prompts, cost comparison, rollback.