Files.exists() 默认不跟随软链接,仅检查路径自身存在性;需先用 Files.readSymbolicLink() 获取目标路径,再转绝对并 normalize 后调用 exists() 判断实际目标是否存在。

Files.exists() 默认不跟随软链接,这是关键前提
Java 的 Files.exists() 默认行为是「不解析软链接」——它只检查路径本身是否存在,不管它是不是链接、指向哪里。也就是说,如果 /tmp/link 是个软链接,Files.exists(Paths.get("/tmp/link")) 返回 true,哪怕它指向一个根本不存在的文件(比如 /nonexistent/target)。
用 LinkOption.NOFOLLOW_LINKS 显式强调不跟随,但没解决目标问题
传入 LinkOption.NOFOLLOW_LINKS 只是确认「这个链接文件自身存在」,和判断「它指向的目标是否存在」完全无关。这反而容易让人误以为加了这个选项就能查目标——其实恰恰相反,它锁死了不跟随的行为。
真正需要的是「跟随软链接并检查最终目标」,而 Files.exists() 没有提供类似 FOLLOW_LINKS 的选项(JDK 一直没加)。所以不能单靠它完成目标。
正确做法:先获取真实路径,再 exists()
必须分两步走:
- 用
Files.readSymbolicLink()获取软链接指向的原始路径(注意:该方法只对软链接有效,对普通文件或硬链接会抛NotLinkException) - 将原始路径转为绝对路径(避免相对路径导致误判),再调用
Files.exists()
示例逻辑:
Path link = Paths.get("/tmp/mylink");
try {
Path target = Files.readSymbolicLink(link);
Path absTarget = link.getParent().resolve(target).toAbsolutePath().normalize();
boolean targetExists = Files.exists(absTarget);
} catch (NotLinkException e) {
// link 不是软链接,直接检查自身
boolean exists = Files.exists(link);
}
注意 normalize() 很重要:它处理 ../、重复 / 等,否则 resolve() 后可能得到错误路径。
更健壮的写法要考虑循环链接和权限问题
实际使用中容易踩的坑:
- 软链接可能形成循环(A → B → A),
readSymbolicLink()不会自动检测,需自己限制跳转次数(比如最多 5 层) - 目标路径可能因权限不足无法访问,
Files.exists()返回false,但这不代表文件不存在,只是不可见 - 某些文件系统(如 NFS、某些容器挂载)可能让
isSymbolicLink()返回false,即使它确实是软链接——建议先用Files.isSymbolicLink()判断类型,再决定是否调用readSymbolicLink()
最简可靠路径判断,往往比想象中更依赖上下文:你得清楚这个路径“应该”是软链接,还是需要通用处理;是否允许跨文件系统;要不要区分“不存在”和“无权限”。这些细节一旦漏掉,exists() 的返回值就容易误导。










