跟著這篇走完,你會在 Java 裡把本地 Ollama 跑通:建一個 Maven 工程,用 LangChain4j 的 ChatModel 跟 qwen3:8b 對話,再換成流式輸出逐字拿回答。整個過程 5 步,每步都有一條命令能驗證自己做對了沒有。文中 localhost:11434 是 Ollama 的預設監聽地址,模型固定用 qwen3:8b;如果你換了端口或模型 tag,把示例值替換掉即可。
先說一個容易踩的版本坑:LangChain4j 1.x 把舊的 ChatLanguageModel 接口刪了,換成 ChatModel,調用方法也從 generate() 改成了 chat()。網上很多教程還停在舊接口,照抄會編譯失敗。下面全部按 1.x 新接口寫。

前置條件
| 項 | 要求 | 說明 |
|---|---|---|
| JDK | 17 或更高 | LangChain4j 1.x 從 Java 17 起支持 |
| Maven | 3.8 或更高 | 本文用 Maven 建工程、拉依賴 |
| Ollama | 0.34.4+,已 pull qwen3:8b |
沒裝過的話先跑《從零跑通 Ollama + Qwen3 本地對話》,把服務拉起來再回來 |
| 記憶體 / 磁盤 | 8 GB 記憶體、6 GB 空閒磁盤 | qwen3:8b 權重 5.2 GB,跑 8B 模型記憶體要兜得住 |
先跑這三條確認環境。
java -version
mvn -v
ollama --version
java -version 輸出 17 或更高、mvn -v 能打出版本號、ollama --version 顯示 0.34.4 以上,就算滿足。ollama: command not found 說明 Ollama 還沒裝,先去 從零跑通 Ollama + Qwen3 本地對話 走完前兩步再回來。模型沒拉的話補一條 ollama pull qwen3:8b,用 ollama list 確認列表裡有它。
第一步:建 Maven 工程並引入依賴
新建目錄和 pom.xml,把 LangChain4j 的 BOM 和兩個模塊引進來。BOM 的作用是把 langchain4j 全家桶的版本統一鎖住,省得核心包和 Ollama 模塊版本參差,那種情況啟動時會報 NoClassDefFoundError(後面常見問題有專門一條)。
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>langchain4j-ollama-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<langchain4j.version>1.20.2</langchain4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.17</version>
</dependency>
</dependencies>
</project>
langchain4j-ollama 是對 Ollama 的實現模塊,langchain4j 是核心 API。BOM 用了 1.20.2,這是截至本文的穩定版;換新版本只改 langchain4j.version 一處。
驗證依賴能不能拉下來。
mvn compile
第一次會下載一堆 jar,最後出現 BUILD SUCCESS 就說明依賴裝齊了。如果報 Could not find artifact 或 NoClassDefFoundError,多半是版本號寫錯或 BOM 沒配好,檢查 langchain4j.version 和 dependencyManagement 是否一致。
第二步:用 ChatModel 完成第一次對話
在 src/main/java/com/example/Main.java 寫入口,用 OllamaChatModel 構建一個模型實例,直接 chat() 拿回答。
package com.example;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
ChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("qwen3:8b")
.temperature(0.8)
.timeout(Duration.ofSeconds(60))
.build();
String answer = model.chat("用一句话介绍你自己,再告诉我你是谁开发的。");
System.out.println(answer);
}
}
注意幾個點。baseUrl 的預設值其實就是 http://localhost:11434,但建議顯式寫出來,排查時少一層猜測。modelName 必須和 ollama list 裡的逐字一致,多一個 : 少一個 tag 都不行。chat(String) 是便捷方法,輸入一句話返回一句話;想要更多控制(多輪、工具、JSON 輸出)要用下面幾版的 chat()。
跑起來。
mvn compile exec:java -Dexec.mainClass=com.example.Main
控制台打出一段像樣的中文回答,就算通了。如果拋 NullPointerException 且棧裡帶 retrofit/OkHttp,別急著查程式碼——多半是模型名寫錯了,Ollama 對不存在的模型返回 404 空響應體,客戶端解析時炸成了 NPE,完整根因看這篇 case。
第三步:帶上上下文聊多輪
ChatModel 是無狀態的,它不記得你上一輪說了什麼。多輪會話要自己把歷史消息拼起來再發。LangChain4j 提供了 UserMessage、AiMessage 這些消息類型,chat(List<ChatMessage>) 接受一組消息。
package com.example;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
ChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("qwen3:8b")
.build();
List<ChatMessage> history = new ArrayList<>();
history.add(UserMessage.from("我叫阿禾,在做支付网关的限流。"));
AiMessage firstReply = model.chat(history).aiMessage();
System.out.println("AI: " + firstReply.text());
history.add(firstReply);
history.add(UserMessage.from("我刚才说我在做什么?"));
AiMessage secondReply = model.chat(history).aiMessage();
System.out.println("AI: " + secondReply.text());
}
}
關鍵在 history 這個列表:每一輪都把用戶消息和 AI 回答按順序塞回去,第二輪再整個發出去。只塞 user 消息、漏掉 assistant 那一條,等於把上一輪的答案抹掉,模型自然接不上話。
驗證。
mvn compile exec:java -Dexec.mainClass=com.example.Main
第二輪迴答裡出現「支付網關」或「限流」,說明上下文真的帶進去了。答非所問就檢查 history 有沒有把 firstReply 加回去。
第四步:改成流式輸出
model.chat() 會等模型把整段話生成完才返回,回答長的時候體驗很乾。換成 OllamaStreamingChatModel,回答會一段一段推給回調。
package com.example;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
public class Main {
public static void main(String[] args) {
StreamingChatModel model = OllamaStreamingChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("qwen3:8b")
.build();
model.chat("用 200 字给我讲一个程序员加班的故事。", new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.print(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("\n[完成]");
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
}
}
三個回調各管一件事:onPartialResponse 每推來一段就打印一段,onCompleteResponse 整段結束收尾,onError 接住例外。注意這裡返回類型是 StreamingChatModel,接口和同步的 ChatModel 不是一個,別引錯 import。
驗證。
mvn compile exec:java -Dexec.mainClass=com.example.Main
回答逐字蹦出來、最後打印 [完成],流式就通了。如果什麼也沒輸出就結束,看 onError 有沒有被觸發,多半是模型名或 baseUrl 的問題,跟第二步同一個查法。
第五步(進階):用 Spring Boot starter 裝配
不想手寫 builder 的話,LangChain4j 提供 Spring Boot starter,設定寫在 application.yml 裡,ChatModel 由容器自動裝配。
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
</dependency>
langchain4j:
ollama:
chat-model:
base-url: http://localhost:11434
model-name: qwen3:8b
temperature: 0.8
timeout: PT60S
@RestController
public class ChatController {
@Autowired
private ChatModel chatModel;
@GetMapping("/chat")
public String chat(@RequestParam String q) {
return chatModel.chat(q);
}
}
驗證。
mvn spring-boot:run
curl "http://localhost:8080/chat?q=你好"
返回中文回答就裝配成功。LangChain4j 的 Spring Boot starter 要求 Java 17 + Spring Boot 3.2,如果你用了 Spring Boot 4,會直接不兼容,看這篇 case。
核心概念
下面這張圖是整條調用鏈路的走向:Java 應用裡的 ChatModel 通過 HTTP 訪問 Ollama 服務,再由 Ollama 調度本地模型生成回答,響應原路返回。

| 概念 | 是什麼 | 類比 |
|---|---|---|
| ChatModel | LangChain4j 1.x 與 LLM 對話的低層接口,chat(String) 是便捷方法 |
像 JDBC 裡的 Connection,一個抽象入口 |
| OllamaChatModel | LangChain4j 對 Ollama 的具體實現,用 builder 構建 | 像數據庫驅動,各家方言都在這層 |
| base-url | Ollama 服務的 HTTP 地址,預設 localhost:11434 |
像數據庫連接串,寫錯就連不上 |
| 模型 tag | 仓库名:规格 的模型標識,如 qwen3:8b |
像鏡像 tag,同一個倉庫下不同規格 |
| BOM | 統一鎖定 LangChain4j 全家桶版本 | 像 parent pom,一處改版本全部生效 |
| StreamingChatModel | 流式接口,回答通過回調增量推送 | 像逐行讀大文件,不用等全部讀完 |
常見問題
NullPointerException,棧裡帶 retrofit / OkHttp
現象:模型名寫錯了,客戶端拋的卻是 NPE,報錯信息裡連模型名都不提。
原因:Ollama 對不存在的模型返回 404、響應體為空,客戶端解析空響應體時炸在 HTTP 層,NPE 只是二次傷害。
編譯報 ChatLanguageModel 找不到
現象:照舊教程寫 ChatLanguageModel,IDE 或編譯直接說這個接口不存在。
原因:LangChain4j 1.x 把舊接口刪了,改名 ChatModel,方法也從 generate() 變成 chat();網上舊教程沒跟上。
啟動報 NoClassDefFoundError
現象:依賴都能拉到,一運行就缺類,報 dev/langchain4j/xxx 找不到。
原因:核心包和 langchain4j-ollama 版本不一致,兩個模塊的類對不上;多半是沒走 BOM 各自寫了版本。
curl: (7) Failed to connect to localhost port 11434
現象:Java 程式一調就報 Connection refused,curl 也連不上 11434,不是超時。
原因:Ollama 服務沒起來,或只綁在別的地址上(容器裡最常見,容器內的 localhost 不是宿主機)。
