1. Tomcat 与 JDK 版本冲突:endorsed.dirs 报错到底怎么回事
如果你最近把 Tomcat 从 7 升到 9、10,或者把 JDK 从 8 换到 11、17,启动时突然蹦出这么一段:
-Djava.endorsed.dirs=D:\apache-tomcat-7.0.57-windows-x64\apache-tomcat-7.0.57\endorsed is not supported. Endorsed standards and standalone APIs in modular form will be supported via the concept of upgradeable modules. Error: Could not create the Java Virtual Machine.先别急着怀疑自己装错了 JDK。这个报错的核心不是“JDK 坏了”,而是endorsed.dirs 这个参数在新版 JDK 里被彻底移除了。Tomcat 7 时代的启动脚本catalina.bat/catalina.sh里默认会拼一个-Djava.endorsed.dirs=...\endorsed,JDK 8 还认这个参数,JDK 9 之后 JVM 直接拒绝启动,于是Could not create the Java Virtual Machine就来了。
那 endorsed.dirs 是干什么的?简单类比:它相当于给 JVM 开了一个“官方 API 覆盖区”。早期 Java 的 XML 解析、CORBA、Web Services 这些标准 API,允许你把新版本的实现 jar 丢进endorsed目录,JVM 会优先加载它们,而不是 JDK 自带的旧实现。这在 JDK 8 及以前是刚需,比如你想用新版 Xerces 覆盖 JDK 自带的解析器。
但 JDK 9 引入模块化(JPMS)之后,官方认为“覆盖 JDK 自带 API”这件事应该由upgradeable modules机制来做,endorsed.dirs和ext.dirs一起被砍掉了。所以你会看到报错里那句提示:will be supported via the concept of upgradeable modules。
这个场景适合谁?三类人最容易踩:
第一类,维护老 Java Web 项目,Tomcat 还是 7 或 8,但服务器上 JDK 被运维统一升到了 11/17,一启动就崩。第二类,本地开发环境装了多个 JDK,JAVA_HOME指向了高版本,但项目用的还是老 Tomcat。第三类,做接口联调时想用统一的 API 通道验证服务是否正常,结果服务压根起不来,误以为是网络或 Key 的问题。
我试过最典型的坑:一台机器上 JDK 8 和 JDK 17 共存,JAVA_HOME没改,Tomcat 7 直接报 endorsed 错误。当时第一反应是去改catalina.bat,但改完发现还有别的兼容问题。所以这篇不只是教你“删掉那行参数”,而是把配置迁移 + 类加载冲突排查 + 用 TaoToken 统一通道验证接口这条链路走通。
先明确一个判断标准:报错里出现endorsed.dirs ... is not supported,就是 JDK 版本 ≥ 9 与老 Tomcat 的组合问题;如果报错是UnsupportedClassVersionError,那是编译版本和运行版本不匹配,属于另一类问题。两者别混。
下面按“先定位、再改配置、再验证”的顺序来,每一步都给可复制的片段。
2. TaoToken 前置准备:统一 Key 与 API 通道
在动手改 Tomcat 配置之前,先把“验证通道”准备好。原因很实际:你改完 endorsed 配置、服务能起来之后,总得确认接口调用是通的。如果每个环境都去配一遍不同的 Key、不同的 Base URL,排查成本会翻倍。用 TaoToken 做统一入口,好处是一个 Key、一个 Base URL,本地、测试、容器里都能复用,出问题时也能快速判断是“服务没起来”还是“通道配错了”。
TaoToken 在这里扮演的角色是统一的模型 API 网关:你拿到一个 Key,就能通过兼容 OpenAI 协议的接口去调用模型,用来做接口连通性验证、日志分析辅助、甚至让模型帮你读 Tomcat 报错。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api (这个不加 UTM)。
具体要准备三样东西,我把它叫“三件套”,后面所有配置都围绕它:
| 项目 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求的前缀,兼容 OpenAI 路径 |
| API Key | 在控制台生成 | 形如sk-...,只显示一次,记得存好 |
| Model ID | 按需选择 | 验证连通性时随便选一个可用模型即可 |
拿 Key 的路径:进控制台 → API Keys → 新建。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。如果你只是想先验证模型能不能通,可以直接用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 试一条消息,不用写代码。
这里要提醒一句:Key 不要硬编码进catalina.bat或提交到 Git。正确做法是放到环境变量里,Tomcat 启动时通过setenv.bat(Windows)或setenv.sh(Linux)注入。这样配置和密钥分离,迁移时只改一处。
为什么要在 Tomcat 排查里引入这个?因为很多“接口 500”其实是服务启动阶段就埋了雷。endorsed 配置没改干净,Tomcat 可能“看起来启动了”,但某个 Servlet 初始化失败,接口一调就报错。这时候你需要一个稳定的外部通道去区分:是应用内部类加载问题,还是外部调用问题。TaoToken 的连通性验证就是干这个的。
如果你后续要做长期编码或 Agent 类任务,可以了解 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,API Keys 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。
准备完这三件套,就可以进入配置环节了。
3. 可复制配置:server.xml 与 catalina.properties 迁移
这一节是核心,给的都是能直接抄的片段。先说清楚原理:老 Tomcat 的 endorsed 机制依赖两个地方——启动脚本里的-Djava.endorsed.dirs参数,以及conf/catalina.properties里的相关配置。JDK 9+ 不认这个参数,所以要么降 JDK,要么把 endorsed 的职责迁移到别的机制上。
方案 A:临时降 JDK(最快,但不推荐长期用)
如果你只是想先让服务跑起来,把JAVA_HOME指回 JDK 8:
:: Windows 临时切换(当前命令行窗口有效) set JAVA_HOME=D:\Java\jdk1.8.0_281 set PATH=%JAVA_HOME%\bin;%PATH%# Linux/macOS 临时切换 export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_281 export PATH=$JAVA_HOME/bin:$PATH改完再启动 Tomcat,endorsed 报错会消失。但这是权宜之计,因为 JDK 8 迟早要淘汰,而且你机器上如果装了高版本 JDK,别的项目可能又需要它。
方案 B:迁移 endorsed 配置到高版本 JDK(推荐)
核心动作有三个:删掉启动脚本里的 endorsed 参数、把 endorsed 目录里的 jar 用别的方式引入、调整catalina.properties。
第一步,找到bin/catalina.bat(Windows)或bin/catalina.sh(Linux),搜索endorsed,把这一行删掉或注释:
:: 删除或注释掉类似这一行 :: set JAVA_OPTS=%JAVA_OPTS% -Djava.endorsed.dirs="%CATALINA_HOME%\endorsed"# Linux 下同样处理 # JAVA_OPTS="$JAVA_OPTS -Djava.endorsed.dirs=$CATALINA_HOME/endorsed"第二步,处理原来放在endorsed目录里的 jar。高版本 JDK 下,这些 jar 应该通过 classpath 或模块路径引入。最简单的方式是放进lib目录,Tomcat 会自动加载:
原路径:%CATALINA_HOME%\endorsed\xercesImpl.jar 新路径:%CATALINA_HOME%\lib\xercesImpl.jar如果你不想动lib,也可以在setenv.bat里显式加到 classpath:
:: conf/setenv.bat(没有就新建) set CATALINA_OPTS=%CATALINA_OPTS% -cp "%CATALINA_HOME%\endorsed\*"第三步,检查conf/catalina.properties。老版本里可能有server.loader或shared.loader指向 endorsed 相关路径,高版本下要确认这些路径存在且不冲突:
# conf/catalina.properties 关键片段 # 确认 common.loader 包含 lib 目录 common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" # 如果有自定义 loader 指向 endorsed,改成实际存在的路径或删掉 # shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jarserver.xml 需要改吗?大多数情况下不需要,因为 endorsed 是 JVM 层参数,不是 Connector 配置。但如果你在server.xml里配了自定义的Loader或Realm依赖了 endorsed 里的类,就要检查:
<!-- conf/server.xml 片段:确认 Context 没有引用已删除的 endorsed 类 --> <Context path="/myapp" docBase="myapp" reloadable="false"> <!-- 不要在这里配 loader 指向 endorsed 目录 --> </Context>关于模块化迁移的补充:JDK 9+ 里,如果你确实需要覆盖某个标准 API,正确做法是用--upgrade-module-path指向新的模块 jar,而不是 endorsed。但对绝大多数 Tomcat 应用来说,直接删掉 endorsed 参数、把 jar 放 lib 就够了,因为现代 Tomcat 版本已经不再依赖 endorsed 机制。
改完记得清一次 Tomcat 的 work 目录,避免旧编译缓存干扰:
rm -rf $CATALINA_HOME/work/*Windows 下就是删掉work文件夹里的内容。这一步很多人忘,结果改了配置还是报老错。
4. 验证请求:确认接口调用正常
配置改完,Tomcat 能起来了,接下来要验证接口是不是真的通。分两层:先验证 Tomcat 本身,再验证通过 TaoToken 的调用链路。
第一层:Tomcat 启动与本地接口
启动 Tomcat,看日志里有没有Server startup in xxx ms:
# Linux $CATALINA_HOME/bin/startup.sh tail -f $CATALINA_HOME/logs/catalina.out:: Windows %CATALINA_HOME%\bin\startup.bat如果启动时间从原来的 11000ms 降到 1400ms 左右,说明类加载冲突基本解决了。然后访问一个本地接口:
curl -i http://localhost:8080/myapp/health返回 200 和预期内容,说明 Tomcat 层没问题。
第二层:通过 TaoToken 验证外部调用
这一步是确认你的应用在调用外部 API 时,通道配置是否正确。用 curl 直接打 TaoToken 的接口:
curl https://taotoken.net/api/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d '{ "model": "your-model-id", "messages": [{"role": "user", "content": "ping"}] }'如果返回正常的 JSON 结构,说明 Key 和 Base URL 都对。如果返回 401,就是 Key 问题;返回 404,多半是路径拼错;连接超时,检查网络出口。
在 Java 应用里验证:如果你用的是 OpenAI 兼容的 Java SDK,配置大概是这样:
// 伪代码示意,具体类名按你用的 SDK 调整 String baseUrl = System.getenv("TAOTOKEN_BASE_URL"); // https://taotoken.net/api String apiKey = System.getenv("TAOTOKEN_API_KEY"); // 用 baseUrl + apiKey 初始化 client,发一条测试消息环境变量在setenv.bat里注入:
:: conf/setenv.bat set TAOTOKEN_BASE_URL=https://taotoken.net/api set TAOTOKEN_API_KEY=sk-你的key成功结果的判断标准:Tomcat 日志无 endorsed 报错、启动时间正常、本地接口 200、TaoToken 接口返回正常 JSON。四个都满足,说明配置迁移和通道验证都过了。
如果只想快速试模型对话,直接用 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 发一条消息,比写代码快。
5. 本篇常见错排查:401、local proxy failed、reading choices
改配置的过程中,报错五花八门。这一节把最常见的几个列出来,对照着查。
报错一:endorsed.dirs ... is not supported反复出现
明明删了catalina.bat里的参数,还是报。原因通常是:JAVA_OPTS或CATALINA_OPTS在别的地方又被拼了一次。检查顺序:catalina.bat→setenv.bat→ 系统环境变量JAVA_OPTS。三个地方都搜一遍endorsed。另外,如果你用的是 IDE 内置 Tomcat,IDE 的 Run Configuration 里也可能有 VM options,别忘了查。
报错二:401 Unauthorized(TaoToken 调用时)
Key 错了、过期了、或者没带上。检查Authorization头是不是Bearer sk-xxx格式,中间有没有多余空格。如果 Key 是从控制台复制的,确认没漏字符。API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。
报错三:local proxy failed或连接被拒
这类报错通常和本地网络配置有关。先确认TAOTOKEN_BASE_URL是不是写成了https://taotoken.net/api,有没有多写斜杠或路径。然后用 curl 单独测一次,排除是 Java 代码问题还是网络问题。如果 curl 通、Java 不通,检查 Java 的 SSL 证书信任链,尤其是老 JDK 可能不认新证书。
报错四:reading choices相关解析错误
这通常出现在你调用模型接口后,解析返回 JSON 时。报错形如Cannot read field "choices"或reading choices failed。原因一般是:返回的不是预期 JSON(可能是错误页 HTML),或者你的解析代码假设了固定结构。先打印原始响应体,确认返回内容。如果是 401/404 的错误 JSON,choices字段当然不存在。
报错五:OAuth相关
如果你在配置某些 CLI 工具或 Agent 时看到 OAuth 报错,先确认是不是把 API Key 模式和 OAuth 模式搞混了。TaoToken 的 API 调用用 Key 就行,不需要 OAuth 流程。如果工具强制走 OAuth,检查它的配置文件里 Base URL 和认证方式。
报错六:UnsupportedClassVersionError
这个和 endorsed 无关,是编译版本高于运行版本。比如用 JDK 17 编译,用 JDK 8 运行。解决方法是统一版本,或者用--release参数编译。
CC Switch / Cline MCP / Codex auth.json 场景:如果你在这些工具里配置 TaoToken,记住三件套要写全——Base URL、Key、Model ID。缺一个都会报错。比如 Codex 的auth.json里,Base URL 和 Key 要对应;Cline 的 MCP 配置里,Model ID 不能空。
排查的核心思路:先看报错关键词,再定位是配置层还是代码层,最后用 curl 做最小验证。别一上来就改代码。
6. 迁移后的稳定接入:把 Key 和通道固定下来
配置改完、报错排完,最后一步是让这套东西稳定下来,别下次升级又踩一遍。
第一,把 JDK 版本和 Tomcat 版本的对应关系写进项目 README。比如“Tomcat 9 + JDK 11 已验证,endorsed 参数已移除”。这样新人接手不用重新踩坑。
第二,setenv.bat/setenv.sh里的环境变量用统一命名,比如TAOTOKEN_BASE_URL、TAOTOKEN_API_KEY,不要散落在多个脚本里。迁移服务器时只改这一个文件。
第三,如果你要做长期编码或 Agent 任务,把 Coding Plan 的配置也固定下来:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 里有完整的参数说明。
第四,Claude Code 这类工具的接入,Base URL 填https://taotoken.net/api,Key 填控制台生成的,Model ID 按文档选。三件套齐全,基本不会出问题。相关配置参考 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。
最后说个实际经验:endorsed.dirs 这个问题,本质是“老配置遇到新运行时”。与其每次升级都手动改,不如在 CI 里加一个检查——启动 Tomcat 后 grep 日志里有没有endorsed或Could not create the Java Virtual Machine,有就 fail。这样问题在流水线就暴露了,不会带到生产。
整套流程走下来,你会发现真正花时间的不是改那行参数,而是搞清楚“为什么改”和“改完怎么验证”。把 TaoToken 作为统一验证通道,能让这个验证过程标准化,换环境也不用重新配。