☰
SpringBoot读取properties中文乱码根因分析及五种解决方案
2026/9/30 12:14:33 网站建设 项目流程

SpringBoot读取properties中文乱码,这问题看着小,真踩上的时候能把人折腾到怀疑人生。尤其是项目里配置文件一多,突然某个环境的提示信息变成了一堆“锟斤拷”或者“??”,第一反应往往是“代码写错了”,结果查了半天发现根本不是代码问题,就是编码链路某个环节断了。这篇文章我就把这个问题的来龙去脉、根因分析和可落地的解决方案完整梳理一遍,按步骤操作基本能一次解决,顺便把我自己踩过的坑也交代清楚,免得你再走弯路。

1. 乱码问题的来龙去脉

1.1 为什么properties文件特别容易出中文乱码

先说个基本事实:properties文件本质上就是一份纯文本文件,它的编码方式完全取决于你用什么工具、在什么系统环境下创建和保存的。Windows下很多编辑器默认保存为GBK,Linux/macOS下默认是UTF-8,而Java的Properties类在读取文件时,历史上默认使用的是ISO-8859-1(也就是Latin-1)编码。

这三方一交叉,乱码就来了。你想想这个场景:开发机是Windows,IDEA里默认文件编码设成了GBK,写了个message.welcome=欢迎使用系统,提交到Git,CI服务器是Linux,构建时Maven或Gradle按UTF-8读取,出来的就是乱码;或者反过来,你在Linux上用vim写了个UTF-8的properties,结果Windows上某个老旧的文本编辑器打开保存一下,文件被转成了GBK,重启SpringBoot后配置项全变成“????”。

这里面最坑的一点是:properties文件的编码问题通常不会导致启动报错,系统能正常启动,只是显示的内容不对。所以排查难度反而比启动异常更大,因为没有异常栈、没有报错日志,只有运行到某个功能时,界面上冒出一串乱码。

我在实际项目中还遇到过一种更隐蔽的情况:同一个properties文件内部混用了两种编码。比如某个同事在Windows上打开文件追加了几行中文注释,用的是GBK,而原有内容都是UTF-8。保存后整个文件在有中文注释的位置附近全部变成乱码,但其他部分正常。这种“局部乱码”比“全局乱码”更让人头疼,因为你会以为是某几行代码写错了。

1.2 认清乱码的几种表现形态

乱码不是只有一种长相,不同表现对应不同根因,先学会区分再动手改。根据我的经验,常见的乱码形态有下面几类:

第一类是控制台输出乱码,表现为运行SpringBoot项目时,System.out.println或日志框架输出的中文变成乱码。这类问题通常和JVM默认字符编码、操作系统终端编码有关,不一定和properties文件有直接关系,但很多人会把它们混在一起排查。

第二类是读取配置项后业务界面上显示乱码,比如从@Value("${message.welcome}")注入的字符串显示为乱码。这种基本可以锁定是properties文件本身在读取时编码不对,或者文件保存时就已经是错的。

第三类是properties文件在IDE里打开就是乱码。这种情况最直观,文件一打开就是“锟斤拷烫烫烫”,说明文件实际字节序列和IDE当前使用的解码方式不匹配,根源在IDE的文件编码设置或者文件本身的编码已经损坏。

第四类是打包部署后乱码,本地开发环境一切正常,打成jar包丢到服务器上运行就乱码。这类问题往往和构建工具、打包插件读取文件时使用的编码有关,比如Maven的project.build.sourceEncoding没有配,或者SpringBoot的spring-boot-maven-plugin在打包时用了默认编码。

我建议你遇到乱码时,先在纸上记录几个信息:哪里看到的乱码(控制台、日志文件、页面还是IDE里)、哪个环境(本地还是服务器)、什么操作后出现的(启动、读取配置、还是显示数据)。这几条信息基本能帮你把问题范围缩小一半以上。

2. 根因剖析:编码链路上到底哪里断了

2.1 properties文件背后的编码机制

要彻底解决中文乱码,得先理解两个底层机制:一个是Properties类的加载方式,另一个是Java源文件编译期的编码处理。

先说Properties类。JDK的java.util.Properties在load(InputStream)方法中,明确规定使用ISO-8859-1编码解析字节流,同时支持\uXXXX形式的Unicode转义序列。这就意味着,如果你直接用Properties.load(new FileInputStream("config.properties"))读取一个UTF-8编码且含中文字符的文件,读出来的内容一定是乱码。

SpringBoot在@PropertySource和Environment体系中,底层也是基于Properties类的加载逻辑,但Spring做了一层封装。org.springframework.core.io.support.PropertiesLoaderSupport和EncodedResource类允许你在加载properties时指定编码。SpringBoot的@PropertySource注解从Spring 4.1开始支持encoding属性,你可以在注解上直接声明文件编码。

再说一个容易被忽略的点:Spring Boot 2.4.0之后引入了全新的配置文件加载机制,支持spring.config.import、多文档properties、profile分组等新特性。这个新机制在读取application.properties时,默认使用UTF-8解析,所以如果你用的是新版SpringBoot且配置文件名是application.properties,那乱码问题的根因和旧版机制可能完全不同。

这里有个很实际的建议:动手之前先搞清楚你用的SpringBoot主版本。如果是2.4以前的,重点排查Properties.load的ISO-8859-1默认编码链;如果是2.4以后的,重点排查文件保存时是否真的存成了UTF-8,以及IDE是否做了编码转换。方向错了,改再多配置都没用。

2.2 读取链路中每一环的编码职责

一条properties配置从磁盘到Java内存,中间经过的每个环节都有自己的编码职责,任何一环脱节就会乱码。我习惯把这条链路拆成四段:

文件保存环节:编辑器必须按你期望的编码保存文件。IDEA中File -> Settings -> Editor -> File Encodings里的Default encoding for properties files默认是UTF-8,且IDEA默认开启Transparent native-to-ascii conversion。这个选项的作用是在编辑器中显示中文,但在保存时自动将非ASCII字符转为\uXXXX转义序列。也就是说,文件落地到磁盘时,中文其实是以ASCII形式存储的,这种文件在任何环境下用Properties.load读取都不会乱码。

构建打包环节:如果你用的是Maven,pom.xml里必须设置project.build.sourceEncoding为UTF-8,否则Maven在复制resources资源文件或编译Java源码时,可能使用平台默认编码(Windows下就是GBK)读取文件,导致文件被以错误编码复制到target目录或改变字节序列。

运行时加载环节:SpringBoot读取application.properties时,2.4以下版本需要你手动指定编码方案,2.4以上版本默认UTF-8。对于自定义的properties文件,可以通过@PropertySource(value = "classpath:xxx.properties", encoding = "UTF-8")显式指定。

JVM输出环节:就算文件读取正确,如果控制台输出时JVM默认字符集不对,依然会显示乱码。启动命令加上-Dfile.encoding=UTF-8可以保证System.out和日志输出按UTF-8处理。

这四段环节中任何一环出问题,最终表现都是中文乱码,但修复方式完全不同。这也是为什么网上的解决方案五花八门,因为大家遇到的是不同环节的问题。

3. 解决方案总览与实操步骤

3.1 方案一:先确定文件本身的真实编码

不管后续怎么改配置,第一步永远是确认文件“实际上”是什么编码。注意,是实际字节序列,不是你在编辑器里看到的显示效果。

最可靠的方式是用命令行工具。Linux和macOS下用file命令:

file -i application.properties

输出结果类似application.properties: text/plain; charset=utf-8,这就说明文件是UTF-8编码。如果是charset=iso-8859-1或charset=us-ascii,那基本可以确定文件保存时编码不对。

Windows下可以用PowerShell读取字节流判断:

$bytes = [System.IO.File]::ReadAllBytes("application.properties") $hex = ($bytes[0..2] | ForEach-Object { $_.ToString("X2") }) -join " " Write-Output $hex

UTF-8编码的文件如果有BOM,前三个字节是EF BB BF;如果没有BOM,需要看具体内容。这里我的经验是:properties文件尽量不要带BOM,因为BOM会让第一行配置项的key变得不可读,SpringBoot解析时可能会把\uFEFF当成key的一部分,导致配置项注入失败。

还有一种判断方式:用IDEA打开文件,看右下角显示的编码是什么。但IDEA显示的是“用当前解码方式读出来的结果”,如果文件本身是GBK、IDEA当前也按GBK解码,右下角显示GBK,这个判断是真实的。如果文件是GBK但IDEA按UTF-8解码,右下角会显示UTF-8且内容是乱码,这时右下角的显示就不代表文件真实编码了。所以IDEA的判断只能作为参考,命令行判断更可靠。

3.2 方案二:IDEA中配置正确的properties编码

大多数乱码问题其实在IDE阶段就能解决。IDEA里有两处关键配置:

第一处是全局编码设置。路径是File -> Settings -> Editor -> File Encodings,把Global Encoding、Project Encoding都设为UTF-8,同时把Default encoding for properties files设为UTF-8。这个设置影响IDEA读取和保存properties文件时采用的编码。

第二处是Transparent native-to-ascii conversion勾选项。我在很多项目里发现,这个选项被一些人误解为“不要勾,否则中文会被转成\uXXXX没法看”。这个理解是错的。这个选项的意义在于:让你在IDE里看到正常的中文,但存储到磁盘时IDEA自动把中文转成\uXXXX形式。因此文件在IDE里可读、在磁盘上对Java解析器友好,两边都兼顾。

我建议你勾选这个选项,并且修改完配置后执行File -> Invalidate Caches / Restart清一下缓存。实际操作中有个反直觉的现象:IDEA对properties的编码识别有缓存,即使你改了全局编码,旧文件可能还是按旧编码显示,必须重启IDE才生效。

如果你手头已经有一个乱码的properties文件,可以手动在IDEA右下角切换编码方式,先按GBK解码,然后Select File Encoding -> Convert,转成UTF-8后保存。注意,这里要选择“Convert”而不是“Reload”,Reload只是重新按新编码读取显示,不会改变磁盘上的文件编码;Convert才会真正把文件字节序列转换。

3.3 方案三:SpringBoot中显式指定编码

如果你不想改IDE、不想转文件编码,也可以直接在SpringBoot代码层面解决。最直接的方式是@PropertySource注解指定编码:

@Configuration @PropertySource(value = "classpath:custom.properties", encoding = "UTF-8") public class CustomConfig { @Value("${custom.message}") private String message; }

这种方式适用于自定义的properties文件。但有个细节需要注意:SpringBoot的application.properties和application-{profile}.properties不能通过@PropertySource指定编码,因为它们是SpringBoot自动加载的,走的是ConfigDataEnvironment机制。

对于application.properties,在SpringBoot 2.4以下版本,可以在启动类里自定义PropertySourceLoader来覆盖默认的properties解析逻辑:

public class Utf8PropertiesLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[]{"properties"}; } @Override public PropertySource<?> load(String name, Resource resource) throws IOException { return new OriginTrackedMapPropertySource(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource, "UTF-8"))); } }

然后在META-INF/factories里注册:

org.springframework.boot.env.PropertySourceLoader=com.example.Utf8PropertiesLoader

这个方法我试过,能用,但比较重,不推荐一般项目使用。更简单的方式是直接改文件编码,统一成UTF-8,让SpringBoot的默认UTF-8解析生效。新版本SpringBoot的配置加载默认UTF-8,前提是文件本身真的是UTF-8。

3.4 方案四:用YAML替代properties

这可能是最适合新项目的一劳永逸方案。YAML文件(.yml)从诞生起就基于UTF-8设计,SpringBoot对YAML的解析也默认采用UTF-8,历史上几乎没见过YAML读取配置导致中文乱码的问题。

迁移成本其实不高,特别是配置项比较规整的情况下。举个例子,原来properties文件:

app.name=用户服务 app.version=1.0.0 app.desc=这是一个演示服务

对应YAML:

app: name: 用户服务 version: 1.0.0 desc: 这是一个演示服务

@ConfigurationProperties绑定完全不用改,@Value取值方式也不用改,SpringBoot会把YAML的层级结构平铺成app.name这样的key。

不过要注意,YAML对缩进有严格要求,不能用Tab,必须用空格。如果一个文件里混用了Tab和空格,解析时会报错,报错信息往往指向“mapping values are not allowed here”,这时候很多人会以为是编码问题,实际上是缩进问题。

我个人的建议是:新项目一律用YAML,老项目如果properties文件数量少且结构简单,也可以逐步迁移。迁移时注意一个坑:YAML里如果值带有特殊字符(比如冒号加空格": "、#号),需要用单引号或双引号包裹,否则会被解析器当成结构符号。

3.5 方案五:构建期强制统一编码

前面说过,本地编码没问题不代表打包部署没问题。Maven和Gradle在构建时需要统一文件编码,否则会出现“本地好好的、服务器上乱码”的经典场景。

Maven项目在pom.xml中加上:

<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>

Gradle项目在build.gradle中:

tasks.withType(JavaCompile) { options.encoding = 'UTF-8' }

这是一个很多人忽略的配置。我用过一个老项目,本地编码全对,一执行mvn package,target目录下的properties文件里的中文就变成了乱码,最后排查发现就是缺少project.build.sourceEncoding导致Maven在复制资源文件时按GBK读取了源文件。这个问题在Windows开发机上尤其高发,因为Windows的中文环境默认编码就是GBK。

另外,SpringBoot的spring-boot-maven-plugin在打包时,会把依赖的jar包重新布局。如果某些资源文件已经被错误编码写入,打包后自然也是错的。所以构建期统一编码是保障最终产物正确性的关键一环。

顺便提一个更隐蔽的场景:如果你用了maven-resources-plugin的filtering功能,也就是资源文件里带占位符自动替换,那么资源插件读取文件的编码也需要显式指定,否则同样存在编码错乱风险。

4. 常见问题与排查技巧实录

4.1 排查乱码问题的定位思路

符合我习惯的排查顺序是这样的:先用命令行确认文件编码 -> 确认IDE编码设置和转换开关 -> 本地启动观察是否乱码 -> 打包后启动观察是否乱码 -> 根据现象锁定环节。

我给你一个可以照抄的排查流程表单:

排查步骤操作判断标准
1. 文件真实编码file -i config.properties必须是UTF-8
2. IDE文件编码IDEA右下角/File Encodings全局和properties均为UTF-8
3. 转换开关Settings -> File EncodingsTransparent native-to-ascii已勾选
4. 本地运行mvn spring-boot:run控制台无乱码
5. 打包运行java -jar app.jar业务界面无乱码
6. JVM编码启动参数加-Dfile.encoding=UTF-8输出正常

每一步如果发现异常,就在那一步停下来修复,不要继续往后,否则问题现象会叠加,更难判断。

我在排查过程中还养成了一个习惯:乱码问题修复后,会把修复前的乱码内容截图或者复制保存一份。因为乱码本身是分析根因的重要线索,不同乱码形态对应不同编码错配方式。比如锟斤拷这种经典乱码,就是UTF-8字节被当成GBK解码后又转回UTF-8产生的;系统这种则是UTF-8字节被Latin-1解码出来的。看到乱码的样子,基本就能反推出是哪两步转码出了问题。

4.2 典型问题速查表

现象根因解决方案
IDE里打开就是乱码文件实际编码与IDE解码方式不一致用命令行确认编码,在IDE中按正确编码Convert
本地启动控制台乱码JVM默认编码非UTF-8,或启动脚本未指定-Dfile.encoding=UTF-8,同时设置JAVA_TOOL_OPTIONS
页面显示乱码但配置文件在IDE里正常properties文件保存时被转为\uXXXX以外的形式勾选IDEA的Transparent native-to-ascii conversion
打包后乱码本地正常Maven/Gradle构建编码未指定设置project.build.sourceEncoding=UTF-8
@Value注入的值为??首次读取时已被错误编码加载确认文件编码后,用@PropertySource(encoding="UTF-8")指定编码加载
部分中文正常部分乱码文件内部混用了多种编码用Python脚本或编辑器统一转码为UTF-8

4.3 Python批量修复脚本与实战

如果你手头有一批已经乱码的properties文件,手动一个一个改效率太低。这时的可行方案是写个Python脚本批量转换。我常用的脚本如下,兼容Windows和Linux:

import os import chardet def convert_to_utf8(file_path): with open(file_path, 'rb') as f: raw = f.read() result = chardet.detect(raw) encoding = result['encoding'] if encoding and encoding.lower() not in ('utf-8', 'ascii'): content = raw.decode(encoding, errors='ignore') with open(file_path, 'w', encoding='utf-8') as f: f.write(content) print(f"Converted {file_path}: {encoding} -> utf-8") else: print(f"Skipped {file_path}: {encoding}") for root, dirs, files in os.walk('src/main/resources'): for file in files: if file.endswith('.properties'): convert_to_utf8(os.path.join(root, file))

chardet库可能不是百分百准确,特别是对短文本的判断容易出错。我的建议是:先挑一个文件跑一遍,确认转换后的内容准确无误,再批量执行。批量执行前一定先备份,用Git的话确保工作区干净,转换后diff一下,看看改变是否符合预期。

这个脚本也处理不了\uXXXX转义和实际中文混存的问题。如果源文件里已经有一部分是转义形式,一部分是真实中文,脚本只会把真实中文的部分做编码转换,转义部分原样保留。最终文件会是“混合态”,但Java解析没问题,因为\uXXXX对Properties类来说是天然合法的。

4.4 几个我踩过的特殊坑

第一,SpringBoot的spring.config.encoding配置。不要被网上一些旧博客误导,spring.config.encoding在SpringBoot 2.4之后其实已经被移除了。我看到过一些老文章推荐在application.properties里写spring.config.encoding=UTF-8,这在旧版本确实存在,但新版本已经不再支持,写了也没用。所以别在这个配置上浪费时间,直接把文件编码弄对就行。

第二,Linux服务器上的locale环境。即便文件编码、打包编码都对了,如果部署服务器的JVM默认locale不是UTF-8,某些日志输出还是会乱码。启动脚本中建议加上:

JAVA_OPTS="-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8"

sun.jnu.encoding这个参数影响JVM对文件名的解析,在中文文件名场景下也容易出问题。这两个参数一起设置,能覆盖绝大多数JVM层面的编码场景。

第三,@PropertySource加载顺序坑。如果你在配置类上用@PropertySource加载一个自定义properties,同时这个properties又定义了spring.datasource.*之类的参数,SpringBoot启动时数据源的自动配置可能比你的@PropertySource更早执行,导致数据源拿不到配置。这不是编码问题,但排查乱码时容易一起碰到,所以提醒一下。

第四,Windows下IDEA与Git的换行符转换。Git在Windows上默认会把换行符从LF转成CRLF,这本身不会导致编码变化,但如果你的properties文件被某个工具以错误编码读取并重写,换行符转换可能会加速“损坏”进程。建议在项目根目录加一个.editorconfig或者.gitattributes,统一换行符为LF,减少干扰因素。

5. 后续扩展思考

如果你已经解决了properties的中文乱码问题,还可以顺手做两件事来提升配置管理的健壮性。

第一,把配置文件中的中文提示信息迁移到数据库表或i18n资源文件中。业务提示信息放在properties里其实是比较初级的做法,更合理的方式是放到数据库中,通过缓存机制加载。这样修改提示内容不用重新打包发布,运维也方便。当然,对于框架级别的静态配置,properties仍然是合适的选择。

第二,如果你的项目用了Spring Cloud Config或者Nacos作为配置中心,配置内容通常以UTF-8存储,且支持动态刷新。这种情况下properties本地文件乱码问题被转移到了配置中心侧,排查思路不变,只是把“本地文件编码”换成“配置中心存储编码”。

第三,考虑引入ConfigData的新特性,将配置分组管理。SpringBoot 2.4之后的spring.config.import支持从多个位置导入配置,如果你始终受困于某个properties文件的编码问题,可以单独把这个文件改成YAML格式,其他保持properties不变。混用格式不会导致问题,SpringBoot对每种格式独立解析。

我个人的体会是,编码问题不像功能bug那样有清晰的报错信息,它更像是一种“玄学”。但只要把文件保存、构建打包、运行时加载、输出显示这四个环节理清楚,问题就是可控的。每次遇到乱码,我都会提醒自己先问一句:文件本身的字节到底是什么?这个问题的答案,往往就已经指向了解决方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询