跟着这篇走完,你会在 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 不是宿主机)。
