怎么用这一页
先按报错的第一行关键词定位,再看「原因」和「修复」。Java 的报错通常包含三样信息:文件与行号、出错的符号或类型、期望值与实际值。读懂这三样,大多数问题不需要搜索引擎。
一个通用方法:
javac -Xdiags:verbose YourFile.java
java -XX:+ShowCodeDetailsInExceptionMessages YourApp
前一个让编译错误更详细,后一个让空指针异常告诉你「哪个变量是 null」。这个开关在 JDK 14 引入、默认关闭;JDK 15 起默认开启。本机 JDK 25 的实际默认值可以这样确认:
$ java -XX:+PrintFlagsFinal -version 2>/dev/null | grep ShowCodeDetails
bool ShowCodeDetailsInExceptionMessages = true {manageable} {default}
编译错误
找不到符号:cannot find symbol
CannotFind.java:3: 错误: 找不到符号
System.out.println(unknownVariable);
^
符号: 变量 unknownVariable
位置: 类 CannotFind
原因:编译器在当前作用域、当前类、当前包和已导入的包里都找不到这个名字。
排查顺序:
- 拼写是否正确(大小写敏感)。
- 变量是否在另一个作用域声明。
- 类是否忘了
import。 - 依赖是否没加进
pom.xml。 - 方法参数类型是否写错,导致重载没匹配上。
不兼容的类型:incompatible types
Incompatible.java:3: 错误: 不兼容的类型: String无法转换为int
int x = "hello";
^
原因:Java 是静态类型语言,不会自动把字符串转成整数。
修复:
int x = Integer.parseInt("42");
或者把变量类型改成 String。不要用强制转换糊弄:(int) "hello" 本身就会编译失败。
缺少返回语句:missing return statement
MissingReturn.java:4: 错误: 缺少返回语句
}
^
原因:方法声明了非 void 返回值,但存在一条执行路径没有 return。
修复:补上所有分支的返回,或者把最后一个分支改成无条件 return。
static int f(int x) {
if (x > 0) return 1;
return 0;
}
公共类文件名不匹配
WrongName.java:1: 错误: 类 OtherName 是公共的, 应在名为 OtherName.java 的文件中声明
public class OtherName { }
原因:一个文件里只能有一个 public 类,且文件名必须与它一致。
修复:把文件重命名为 OtherName.java,或者去掉 public(如果不需要跨包访问)。
程序包不存在:package does not exist
原因:import 的包不在 classpath 上,通常是依赖没加或模块没声明。
排查:
mvn -q dependency:tree
javac -cp "lib/*" -d out $(find src -name '*.java')
Maven 项目先 mvn -q compile,看依赖是否下载成功;模块化项目检查 module-info.java 的 requires。
可能尚未初始化变量
int x;
if (condition) x = 1;
System.out.println(x); // 编译错误
原因:编译器无法证明 x 在所有路径上都被赋值。
修复:给默认值,或补全 else 分支。
不可达语句:unreachable statement
return;
System.out.println("never");
原因:return、throw、无限循环之后的代码永远不会执行。
修复:删掉不可达代码,或调整控制流。
运行错误
找不到或无法加载主类
错误: 找不到或无法加载主类 com.plume.library.LibraryApp
原因: java.lang.ClassNotFoundException: com.plume.library.LibraryApp
原因:classpath 上找不到这个全限定类名。
排查:
- 类名是否写全(包名 + 类名)。
-cp是否指向 class 文件所在目录,而不是src。- 包声明与目录结构是否一致(
com/plume/library/LibraryApp.class)。 - jar 是否包含这个类:
jar tf app.jar | grep LibraryApp。
修复:
javac -d out src/main/java/com/plume/library/LibraryApp.java
java -cp out com.plume.library.LibraryApp
NoClassDefFoundError
原因:编译时有这个类,运行时找不到;或者类的静态初始化失败。
与 ClassNotFoundException 的区别:后者是「从来没找到」,前者是「找过但加载失败/缺失」。
排查:
- 依赖是否打进产物(
mvn dependency:tree、jar tf)。 - 静态块是否抛异常(看
Caused by)。 - 是否有版本冲突导致同一个类被替换。
UnsupportedClassVersionError
Exception in thread "main" java.lang.UnsupportedClassVersionError:
LibraryApp has been compiled by a more recent version of the Java Runtime
(class file version 69.0), this version of the Java Runtime only recognizes
class file versions up to 52.0
原因:class 文件版本高于 JVM 支持版本。69 对应 JDK 25,52 对应 JDK 8。
修复:
javac --release 21 -d out src/main/java/com/plume/library/LibraryApp.java
或者升级运行环境的 JDK。不要用 -source/-target 代替 --release。
NoSuchMethodError
原因:编译时链接了高版本 API,运行时加载的是低版本类。常见于依赖冲突。
排查:
mvn -q dependency:tree -Dincludes=com.example:some-lib
修复:在 pom.xml 里显式声明正确版本,或用 dependencyManagement 统一版本。
NullPointerException
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "<local1>" is null
at Npe.main(Npe.java:4)
原因:对 null 调用方法、访问字段、拆箱、数组长度。
修复:
- 用
Objects.requireNonNull在边界处快速失败。 - 用
Optional表达「可能没有值」,但不要用它替代所有判空。 - 检查集合查找结果:
map.get(key)可能返回null。 - 自动拆箱时尤其小心:
Integer为null会抛 NPE。
ArrayIndexOutOfBoundsException
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
Index 5 out of bounds for length 3
原因:索引超出 0..length-1。
修复:检查循环边界(i < array.length),检查 subList/substring 的结束索引是否越界。
ClassCastException
Exception in thread "main" java.lang.ClassCastException:
class java.lang.String cannot be cast to class java.lang.Integer
原因:强转的类型与实际类型不兼容。
修复:先用 instanceof 判断,或用模式匹配:
if (obj instanceof Integer i) {
System.out.println(i + 1);
}
IllegalArgumentException
原因:参数值不合法(负数、空字符串、越界范围)。
修复:在方法入口校验参数,抛出带具体信息的异常;调用方捕获后给用户可读提示。
ConcurrentModificationException
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1096)
原因:用增强 for 遍历集合时,直接调用集合的 remove/add。
修复:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("a")) it.remove();
}
或使用 removeIf:
list.removeIf(s -> s.equals("a"));
或使用 CopyOnWriteArrayList(读多写少场景)。
ExceptionInInitializerError
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.ArithmeticException: / by zero
at InitErr.<clinit>(InitErr.java:2)
原因:静态初始化块或静态字段初始化抛异常。
修复:看 Caused by 找到真正的异常;把可能失败的静态初始化改成惰性初始化或显式方法。
OutOfMemoryError
| 类型 | 常见原因 | 排查 |
|---|---|---|
Java heap space | 对象太多/太大、泄漏 | 堆转储、jmap、MAT |
Metaspace | 动态生成类太多 | 检查反射/代理/字节码生成 |
Direct buffer memory | NIO 直接内存未释放 | 检查 ByteBuffer.allocateDirect |
unable to create native thread | 线程数过多 | 检查线程池、ulimit |
卷一只需知道:OOM 不是「加内存」就完事,要先看是哪块内存。卷四会讲堆转储和 NMT。
StackOverflowError
原因:递归太深或无限递归。
修复:加终止条件,或改成迭代。默认栈大小可以用 -Xss 调整,但通常先改代码。
编码与文件
中文乱码
原因:写入和读取用了不同编码,或终端/文件/数据库编码不一致。
排查:
file -I books.csv
xxd -l 16 books.csv
修复:读写都显式指定 UTF-8:
Files.writeString(path, text, StandardCharsets.UTF_8);
String text = Files.readString(path, StandardCharsets.UTF_8);
如果文件来自 Excel,可能带 BOM。读取时用 BufferedReader 并跳过 ,或先用工具去掉 BOM。
路径找不到
原因:相对路径的基准是「启动 JVM 的当前目录」,不是源码目录,也不是 jar 所在目录。
修复:用 Path.of(...).toAbsolutePath() 打印实际路径;生产环境用配置指定数据目录,不要依赖当前目录。
WatchService 收不到事件
原因:监听的是目录,不是文件;某些编辑器保存时是「写临时文件 + 重命名」,事件类型不是 ENTRY_MODIFY。
修复:监听目录,处理 ENTRY_CREATE、ENTRY_DELETE、ENTRY_MODIFY 三类事件;对重命名做去抖。
Maven 与构建
mvn -version 的 Java 版本不对
原因:Maven 用 JAVA_HOME 指向的 JDK,而不是 PATH 里的 java。
修复:
echo $JAVA_HOME
which -a java
mvn -version
把 JAVA_HOME 指向 JDK 25,重新打开终端。
依赖冲突
症状:NoSuchMethodError、ClassNotFoundException、行为与文档不一致。
排查:
mvn -q dependency:tree
mvn -q dependency:tree -Dincludes=com.fasterxml.jackson.core
修复:用 dependencyManagement 统一版本,或排除传递依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>some-lib</artifactId>
<version>1.2.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
测试没跑
原因:测试类命名不符合 Surefire 默认规则(*Test、Test*、*Tests、*TestCase),或测试目录不在 src/test/java。
修复:重命名测试类,或配置 Surefire 的 includes。
mvn package 后 jar 不能运行
症状:
hello.jar中没有主清单属性
原因:jar 的 META-INF/MANIFEST.MF 没有 Main-Class。
修复:用 maven-jar-plugin 配置 mainClass,或用 maven-shade-plugin 打可执行 fat jar。第 17 章有完整配置。
构建结果不可复现
原因:时间戳、文件顺序、依赖版本范围、本地缓存。
修复:固定依赖版本,使用 maven-enforcer-plugin 锁定版本,开启 project.build.outputTimestamp,在干净环境验证。
HTTP 与服务
Address already in use
原因:端口被占用。
排查:
lsof -i :8080
修复:换端口,或结束占用进程。不要在代码里硬编码端口,用配置或环境变量。
Connection refused
原因:目标服务没启动、端口写错、防火墙拦截。
排查:先用 curl 或 nc 确认服务可达,再查客户端代码。
HTTP 请求超时
原因:没有设置连接/读取超时,默认可能无限等待。
修复:
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/api"))
.timeout(Duration.ofSeconds(5))
.GET()
.build();
调试流程
遇到问题时,按这个顺序缩小范围:
- 读第一行报错:类型、文件名、行号。
- 读
Caused by:真正的根因通常在最后一层。 - 最小复现:把可疑代码复制到单独文件,用
javac/jshell跑。 - 打印中间值:
System.out.println不丢人,先确认假设。 - 检查环境:JDK 版本、classpath、编码、时区、当前目录。
- 查官方文档:Javadoc 和 Release Notes 比博客可靠。
- 再改代码:一次只改一个变量,改完立刻验证。
常用诊断命令
# 版本与环境
java -version
javac -version
mvn -version
echo $JAVA_HOME
# 类与依赖
jar tf app.jar | head
jdeps --multi-release 21 -summary out
mvn -q dependency:tree
# 运行时
jps -l
jcmd <pid> Thread.print
jcmd <pid> GC.heap_info
jstack <pid>
jmap -dump:format=b,file=heap.hprof <pid>
# 端口
lsof -i :8080
jps、jcmd、jstack、jmap 都在 JDK 的 bin 下。卷四会系统讲这些工具。
一页记忆
- 编译错误看「符号、类型、行号」;运行错误看「异常类、消息、Caused by」。
- 找不到类:先查 classpath 和包名,再查依赖。
- 版本错误:用
--release统一编译目标。 - 空指针:在边界用
Objects.requireNonNull,拆箱前判空。 - 并发修改:用
Iterator.remove或removeIf。 - 乱码:读写都显式 UTF-8,检查 BOM。
- Maven 冲突:
dependency:tree+dependencyManagement。 - jar 不能跑:检查
Main-Class和依赖是否打进去。 - OOM:先分清堆、Metaspace、直接内存、线程,再谈加内存。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。