Files.probeContentType() 默认不依赖文件后缀,仅基于内容探测,故常返回null;需手动注册基于后缀的FileTypeDetector或改用Tika/mime-util等库实现可靠MIME识别。

Files.probeContentType() 不会根据文件后缀名自动返回 MIME 类型——它依赖操作系统或 JVM 内置的探测机制,**默认根本不看后缀名**。想靠它“按扩展名查 MIME”,直接调用会失败或返回 null。
为什么 probeContentType() 经常返回 null
该方法底层调用的是 FileTypeDetector 服务,JDK 默认只注册了基于文件内容(magic bytes)的探测器(如 BasicFileTypeDetector),对纯文本、无签名的文件(.txt、.json、.yml)或后缀不标准的文件,几乎必然返回 null。Windows/macOS 上还可能受系统 MIME 数据库影响,但不可控、不一致。
- 常见错误现象:
Files.probeContentType(Paths.get("report.xlsx"))返回null,哪怕文件真实存在且后缀正确 - 即使文件有内容,
.csv、.log、.md这类无固定 magic header 的格式也大概率探测失败 - OpenJDK 和 Oracle JDK 行为略有差异,但都不保证后缀 fallback
正确做法:手动注册基于后缀的 FileTypeDetector
必须显式注册一个按 fileName 后缀映射 MIME 的探测器,JVM 才会在内容探测失败时尝试它。核心是继承 FileTypeDetector 并重写 probeContentType() 方法。
- 探测器需通过
ServiceLoader注册:在META-INF/services/java.nio.file.spi.FileTypeDetector中写入实现类全限定名 - 实现中应先检查
path.getFileName().toString(),用String.endsWith()或Path.getFileName().toString().substring()提取后缀,再查表返回(避免依赖Files.getFileExtension(),它不处理多点名如archive.tar.gz) - 推荐使用
java.util.Map静态初始化常用映射,例如:".pdf" → "application/pdf"、".svg" → "image/svg+xml" - 务必在探测逻辑开头加
if (path == null || !Files.isReadable(path)) return null;,避免 NPE 或权限异常
更轻量的替代方案:用 Apache Tika 或 mime-util
如果不想折腾 SPI 机制,第三方库更省心。Tika 的 Tika.detect(InputStream) 或 Tika.detect(String filename) 会自动 fallback 到后缀匹配;mime-util 则专注后缀映射,启动快、无依赖。
- Tika 示例:
new Tika().detect("data.csv")直接返回"text/csv",无需文件存在 -
mime-util示例:MimeUtil2.getExtensionMimeType("xls")→"application/vnd.ms-excel"(注意传入不含点的扩展名) - 注意 Tika 默认加载大量解析器,若只用后缀匹配,可禁用内容探测:
new Tika(new ParseContext()).detect(...)性能更好 - 这两个库都比自研探测器兼容性高,尤其对
.webp、.avif、.toml等新格式支持及时
真正关键的不是“怎么让 probeContentType 工作”,而是意识到它设计上就不是为后缀服务的——硬要它干这事,就得补全探测链路,否则永远踩 null 坑。实际项目里,90% 场景用 Map.of(".json", "application/json", ...) 手动查表,比搞 SPI 更快更稳。










