StackTraces

Spring Boot 启动失败:先定阶段,再进分支

把启动失败拆成 6 个阶段、12 条可对号入座的分支:先读 Description 与 Action,再确认卡在哪个启动阶段,最后按报错签名展开分支,用判据式命令锁定成因。大多数启动失败不需要改代码,问题出在环境或配置。

排查全景图
全景导航 · 6 阶段 × 12 条分支,先在图上定位现象,再进下方路径

按首个可操作的异常分流

先读完整异常链与 Failure Analysis,记录运行环境和生效配置,再按具体签名选择路径。每条路径统一为六段:当前环境、报错识别、快速解法、验证、解法无效时、相关。

12 条路径 →
01 · 环境

当前环境

先记录 Spring Boot 版本、构建 JDK、编译工具链、部署镜像、容器运行时和实际启动命令。容器运行时必须在容器内核验 java -version,不能用构建机或宿主机版本代替。

  • 确认报错类来自应用产物还是第三方 JAR,并核对框架、插件和依赖的 Java 支持矩阵
  • 记录编译配置的 --release、toolchain、source/target,以及多阶段构建最终镜像实际带入的 JRE/JDK
02 · 签名

报错识别

异常签名 / SIGNATURE
java.lang.UnsupportedClassVersionError:
  com/acme/App has been compiled by a more recent version
  of the Java Runtime (class file version 61.0),
  this version only recognizes class file versions up to 55.0
常见变体 / VARIANTS
Error: A JNI error has occurred, please check your installation and try again
Unsupported major.minor version 61.0
java.lang.IllegalArgumentException: Unsupported class file major version 61
NoClassDefFoundError: Could not initialize class com.acme.App
  • UnsupportedClassVersionError → 运行期类加载失败,提示里会给出「需要的版本」和「当前上限」两个数字
  • Error: A JNI error has occurred → 启动器层的提示,紧跟其后的才是 UnsupportedClassVersionError
  • Unsupported major.minor version → 旧版 Java 的同义报错,数字规则完全相同
  • Unsupported class file major version 61 → 构建或运行期的字节码工具(ASM、ByteBuddy、CGLIB)读不懂新版本
  • NoClassDefFoundError / ExceptionInInitializerError → 常是版本不兼容引发的次生错误,先回到最初的版本异常

class 文件版本对照:52.0=Java 8,55.0=Java 11,61.0=Java 17,65.0=Java 21。不要把 56.0 读成「Java 56」。

03 · 安全操作

快速解法

升级运行时是首选;必须留在旧 JVM 时用 release 重新编译。

升级运行时(首选)— 改基础镜像,光改构建机没用

# Dockerfile
FROM eclipse-temurin:17-jre
COPY target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

必须留在旧 JVM — 用 release 重新编译

<properties>
  <java.version>11</java.version>
</properties>

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <release>11</release>
  </configuration>
</plugin>

报错类来自第三方依赖 — 先确认是哪个 JAR

mvn dependency:tree -Dincludes=com.example:legacy-lib
04 · 确认

验证

  1. 在最终部署镜像或容器内执行 java -version,确认启动脚本没有调用另一套 JRE
  2. 检查报错类所在 JAR 的 class major 与运行 JVM 上限,确认构建产物和镜像标签已更新
  3. 重新启动应用,确认不再出现 UnsupportedClassVersionError,并执行健康检查
05 · 继续诊断

解法无效时

  1. 容器内版本仍不符时,检查多阶段 Docker 构建的最终阶段、基础镜像、PATH、JAVA_HOME
  2. class 版本仍过高时,用 jar tf 定位报错类所属 JAR,再检查依赖树
  3. 版本兼容后仍启动失败时,以新的最深层异常继续分流
06 · 相邻原因

相关

  • class file version 56.0 是 Java 12;61.0 是 Java 17,55.0 是 Java 11
  • Unsupported major.minor version 是较旧 Java 的类似版本错误;NoSuchMethodError 更偏向二进制 API 版本冲突
  • 构建机、宿主机、容器镜像和容器运行时可能使用不同 Java;最终以真正执行启动命令的 JVM 为准

常见问题

启动报 UnsupportedClassVersionError(Unsupported major.minor version)怎么解决?
编译产物的字节码版本高于运行期 JRE,容器里常见于构建机用 JDK 17、镜像却带 JRE 11。首选升级运行时基础镜像,并在容器内核验 java -version;必须留在旧 JVM 时,用 maven-compiler-plugin 的 <release> 重新编译,再用 dependency:tree 确认报错类不是第三方 JAR 带入的。
启动报 Could not resolve placeholder,占位符为什么解析不出来?
Environment 里找不到配置中引用的键,来源可能是 application.yml、环境变量或外部配置。先把异常里的键名抄下来,逐处核对拼写与真实来源,并确认当前激活的 Profile 与容器内的环境变量、挂载文件是否一致。
运行期抛 NoSuchMethodError 或 ClassNotFoundException,是依赖冲突吗?
多为同一个库在依赖树里存在多个版本,或 Spring Cloud 与 Spring Boot 版本随意组合,运行时加载到了不兼容的类。用 dependency:tree 定位冲突并以 dependencyManagement 锁定版本,按官方版本矩阵对齐组合。
启动报 Port 8080 was already in use 怎么处理?
端口已被其它进程占用。二选一:用 lsof -i :8080 找到并停掉占用进程,或在配置里换一个端口。容器场景要分别核对容器内监听端口、宿主机映射端口与同一网络命名空间里的进程,避免改错了一层。
启动报 Failed to configure a DataSource 是什么原因?
引入了数据源相关的 starter,却没有可用的连接配置,自动配置装配不出 DataSource。看最深层 Caused by 对号入座:补上 spring.datasource.url 等配置,或在确实不需要数据库时排除 DataSourceAutoConfiguration。
配好了 spring.datasource.url 仍连不上库,报 Access denied 或 Communications link failure?
配置已经存在,但连库被拒或网络不通。Access denied 多为账号密码或授权问题,Communications link failure 多为地址端口不可达。先把报错文案对上,再分别核对主机、端口、库名、凭据与网络连通性。
启动报 UnsatisfiedDependencyException,Bean 注入失败怎么办?
某个 Bean 缺少它依赖的另一个 Bean,通常是目标类不在扫描根包内、条件装配没生效或模块依赖缺失。报错已经指明是哪个类、哪个字段,按这条线索核对组件扫描范围、Profile 与条件注解,再对症修复。
报 A bean with that name has already been defined 怎么解决?
两个配置类或组件定义了同名 Bean。成因唯一,全局搜这个名字找到所有定义处,删除重复定义或给其中一个改名,不要靠 Bean 覆盖顺序硬扛。
构造器注入报 BeanCurrentlyInCreationException,循环依赖怎么破?
两个 Bean 通过构造器互相依赖形成闭环,Spring 无法在构造阶段提前暴露半成品。首选重构解耦,例如抽出公共依赖或调整职责;急着上线时才用 @Lazy 打破闭环。先确认是真的成环,而不是创建期间的二次引用。
启动日志中文乱码怎么修?
文件编码与控制台编码不一致,不是代码问题。把 JVM 与终端两边统一为 UTF-8,启动时加 -Dfile.encoding=UTF-8,并确认终端与容器 locale 同样支持 UTF-8。
启动就报 OutOfMemoryError: Java heap space?
启动期 OOM 基本都是堆上限太小,而不是内存泄漏。先调大 -Xmx 后重试;调大仍失败,再看是否有大对象在启动期一次性加载,才考虑深挖泄漏。
报 Unable to start embedded Tomcat,端口和内存都排除了?
排除端口占用与堆内存后,剩下的多与系统资源有关,例如线程数、文件句柄上限。先看完整的 Caused by 链确认具体资源类型,再调大对应的系统资源上限后重试。