ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Maven构建报错Input length = 1?资源文件编码问题排查与修复指南

Maven构建报错Input length = 1?资源文件编码问题排查与修复指南

1. 这个报错到底在说什么:从"Input length = 1"读懂Maven的沉默抗议

第一次看到Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources后面跟着一句莫名其妙的Input length = 1,我盯着屏幕愣了大概半分钟。这行日志的诡异之处在于:它既没有告诉你哪个文件出了问题,也没有堆栈信息指向具体代码行,就干巴巴甩出一句"输入长度为1",然后构建就挂了。如果你正在赶进度,这种报错绝对能让人血压飙升。

先把结论摆在前面:这个报错99%的情况是资源文件编码问题导致的,具体来说,是maven-resources-plugin在读取某个资源文件时,遇到了它无法用当前配置的字符集解析的字节序列。Input length = 1是 Java 的CharsetDecoder在解码时抛出的MalformedInputException的经典消息,意思是"我拿到了一段长度为1的输入,但它不符合我当前字符集的编码规则,我解不了"。

为什么这个报错这么难排查?因为 Maven 在资源处理阶段是批量扫描src/main/resources和src/test/resources目录的,它不会告诉你具体是哪个文件触发了异常。你只能靠"二分法"或者"编码检测"来定位。而且这个报错在不同 Maven 版本、不同 JDK 版本下的表现还不完全一样,有时候会带上文件名,有时候就是光秃秃一句话。

这篇文章我会把这个问题彻底拆开讲清楚:从maven-resources-plugin的工作机制,到Input length = 1的底层触发原理,再到完整的排查链路和多种修复方案。不管你是刚接触 Maven 的新手,还是被这个报错折磨过的老手,都能从里面找到可以直接抄作业的步骤。

提示:如果你现在正卡在这个报错上,先别急着改pom.xml的编码配置。很多时候问题不在配置本身,而在于某个资源文件里混入了"脏字节"。盲目改配置可能掩盖问题,后面还会以更诡异的方式爆出来。

2. maven-resources-plugin 在构建流程里到底干了什么

2.1 资源处理阶段的位置与职责

要理解这个报错,得先搞清楚maven-resources-plugin在整个 Maven 生命周期里扮演什么角色。Maven 的默认生命周期里,process-resources阶段绑定的是resources:resources目标,process-test-resources阶段绑定的是resources:testResources。这两个目标做的事情很朴素:把src/main/resources和src/test/resources目录下的文件,复制到target/classes和target/test-classes目录里。

听起来很简单对吧?但"复制"这个动作在 Maven 里不是简单的文件拷贝。它涉及到几个关键处理:

  • 变量替换(filtering):如果开启了 filtering,Maven 会把资源文件里的${...}占位符替换成pom.xml里定义的属性值。这个过程需要把文件内容读成字符串,替换后再写回去。
  • 编码转换:读取文件时用一套字符集,写入时用另一套字符集。如果两边不一致,或者源文件的字节序列不符合声明的字符集,就会抛异常。
  • 文件过滤:根据includes和excludes配置决定哪些文件需要处理,哪些直接跳过。

Input length = 1这个报错,就发生在"读取文件"这一步。maven-resources-plugin底层用的是 Plexus Utils 的FileUtils.fileRead()或者类似的 IO 工具,这些工具在读取文件时会用InputStreamReader配合指定的Charset来解码字节流。当字节流里出现了不符合该字符集规则的字节序列时,CharsetDecoder就会抛出MalformedInputException,而Input length = 1正是这个异常的消息内容。

2.2 为什么偏偏是"length = 1"

这里有个很多人困惑的点:为什么是"长度为1"?难道文件里只有一个字符有问题?

其实Input length = 1里的"1"不是指文件里只有一个坏字节,而是指CharsetDecoder在解码过程中,当前这一步拿到的输入缓冲区里,从当前位置开始有1个字节无法被解码。换句话说,解码器是逐段处理字节的,当它发现某个位置的字节序列不符合规则时,就会报告"我这里拿到了长度为1的输入,但它不合法"。

举个具体的例子。假设你的文件是 UTF-8 编码的,但pom.xml里配置的project.build.sourceEncoding是GBK。UTF-8 的中文字符通常是3个字节,比如"中"是E4 B8 AD。当解码器用 GBK 去解这3个字节时,GBK 是双字节编码,它会把E4 B8当成一个字符,剩下AD单独一个字节。这个AD在 GBK 里可能不是一个合法的首字节,解码器就会报Input length = 1。

反过来也一样。如果文件是 GBK 编码,配置是 UTF-8,GBK 的中文字符是2个字节,UTF-8 解码器看到这两个字节时,可能第一个字节是合法的 UTF-8 首字节,但第二个字节不符合后续字节的规则,同样会报错。

还有一种常见情况:文件本身是 UTF-8 无 BOM 的,但里面混入了从 Windows 记事本保存时带上的 BOM 头(EF BB BF),或者从某些系统复制粘贴时带入了不可见的控制字符。这些"脏字节"在特定编码下就会触发解码异常。

2.3 报错信息为什么经常"缺胳膊少腿"

正常情况下,MalformedInputException应该带上出错的位置信息,但maven-resources-plugin在捕获异常后,往往只把异常消息原样往上抛,没有附加文件名和行号。这就导致你看到的日志只有一句Input length = 1,完全不知道是哪个文件惹的祸。

更麻烦的是,Maven 的日志级别和插件版本会影响报错的详细程度。有些版本的maven-resources-plugin会在报错前打印正在处理的文件名,有些版本则什么都不打。如果你用的是 3.2.0 这个版本,恰好它的日志输出比较"吝啬",排查起来就更费劲。

注意:不要指望通过加-X参数就能直接看到出错文件名。-X会输出大量调试信息,但资源处理阶段的文件级日志往往还是不够明确。真正有效的定位手段是下面要讲的"二分排查法"和"编码检测法"。

3. 定位问题文件:一套可复现的排查链路

3.1 第一步:确认报错发生在哪个资源目录

在动手排查之前,先缩小范围。maven-resources-plugin会处理两个目录:src/main/resources和src/test/resources。报错可能发生在任意一个。

你可以先执行一次只处理主资源的构建:

mvn process-resources -X 2>&1 | grep -A 5 "maven-resources-plugin"

如果报错出现在这一步,问题就在src/main/resources。如果这一步通过了,再执行:

mvn process-test-resources -X 2>&1 | grep -A 5 "maven-resources-plugin"

这样就能确定是主资源还是测试资源出了问题。别小看这一步,很多人一上来就全局搜索,结果在错误的方向上浪费了大量时间。

3.2 第二步:用二分法锁定具体文件

确定目录后,接下来要找出具体是哪个文件。最笨但最有效的方法是二分法:

  1. 把src/main/resources下的文件分成两半,暂时把其中一半移到临时目录。
  2. 执行mvn process-resources,看是否还报错。
  3. 如果还报错,说明问题在剩下的一半里;如果不报错了,说明问题在被移走的那一半里。
  4. 对有问题的那一半重复上述步骤,直到锁定单个文件。

这个方法听起来很土,但在没有明确日志的情况下,它是最高效的。一个中等规模的项目,资源文件通常也就几十到几百个,二分法最多七八轮就能定位到具体文件。

如果你嫌手动移动文件太麻烦,可以写个简单的脚本辅助:

#!/bin/bash # 二分排查脚本示例 RESOURCE_DIR="src/main/resources" TEMP_DIR="/tmp/resource_backup" # 备份所有资源 cp -r "$RESOURCE_DIR" "$TEMP_DIR" # 这里需要你手动配合,每次移动一半文件后运行 mvn process-resources # 根据结果决定保留哪一半

当然,更省事的办法是用下面的编码检测法直接扫描。

3.3 第三步:用 file 命令和 iconv 快速检测编码

在 Linux 或 macOS 上,file命令可以快速查看文件的编码信息:

file -i src/main/resources/*.properties file -i src/main/resources/*.xml file -i src/main/resources/**/*.json

file -i会输出类似charset=utf-8或charset=iso-8859-1的结果。如果你发现某个文件显示的不是utf-8,而你的项目配置是 UTF-8,那它很可能就是罪魁祸首。

更精确的检测可以用iconv尝试转换:

iconv -f UTF-8 -t UTF-8 src/main/resources/config.properties -o /dev/null

如果文件不是合法的 UTF-8,iconv会报错并指出出错的位置。这个命令的好处是它能告诉你具体在第几行第几列出了问题,比 Maven 的报错信息有用得多。

在 Windows 上,可以用 PowerShell 来检测:

Get-Content -Path "src\main\resources\config.properties" -Encoding UTF8 -ErrorAction Stop

如果文件不是 UTF-8,PowerShell 会抛出异常。

3.4 第四步:检查文件里的"隐形杀手"

有些文件用file命令检测显示是 UTF-8,但依然会触发Input length = 1。这种情况通常是文件里混入了不可见字符。常见的"隐形杀手"包括:

  • BOM 头:Windows 记事本保存 UTF-8 文件时会自动加上EF BB BF。虽然 BOM 本身是合法的 UTF-8 字节序列,但某些工具在处理时会把 BOM 当成内容的一部分,导致后续解析出错。
  • 零宽字符:从网页或聊天工具复制代码时,经常会带入零宽空格(E2 80 8B)、零宽非连接符(E2 80 8C)等不可见字符。
  • 控制字符:比如0x00到0x1F之间的某些控制字符,在 XML 和 properties 文件里是非法的。
  • 混合换行符:Windows 的\r\n和 Unix 的\n混用,虽然通常不会导致编码错误,但在某些解析场景下会引发问题。

检测这些隐形字符,可以用hexdump或xxd查看文件的原始字节:

xxd src/main/resources/config.properties | head -20

如果文件开头出现efbb bf,那就是 BOM。如果中间出现e280 8b之类的序列,那就是零宽字符。

用grep也可以快速查找 BOM:

grep -rl $'\xEF\xBB\xBF' src/main/resources/

这个命令会列出所有带 BOM 的文件。

4. 修复方案:从根因出发的四种处理策略

4.1 策略一:统一项目编码配置(推荐)

最根本的解决办法是在pom.xml里显式声明编码,并确保所有资源文件都用同一种编码保存。标准的配置如下:

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

这两个属性分别控制源码编译和报告输出的编码。maven-resources-plugin会读取project.build.sourceEncoding作为资源处理的默认编码。

但光配置这个还不够。你还需要在maven-resources-plugin的配置里显式指定编码:

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.2.0</version> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>

为什么要两处都配?因为project.build.sourceEncoding是一个"约定",很多插件会读取它,但不是所有插件都严格遵守。在插件自己的configuration里显式指定encoding,是确保万无一失的做法。

配好之后,把所有资源文件统一转成 UTF-8 编码。在 Linux/macOS 上可以用iconv批量转换:

find src/main/resources -type f \( -name "*.properties" -o -name "*.xml" -o -name "*.json" \) -exec sh -c ' for file do iconv -f GBK -t UTF-8 "$file" -o "$file.utf8" 2>/dev/null && mv "$file.utf8" "$file" done ' sh {} +

在 Windows 上,可以用 Notepad++ 的"编码"菜单批量转换,或者用 PowerShell 脚本处理。

4.2 策略二:处理 properties 文件的特殊编码规则

这里要特别说一下.properties文件。Java 的Properties类在读取文件时,默认使用 ISO-8859-1 编码,而不是 UTF-8。这是 Java 历史遗留问题,很多人不知道。

这意味着,即使你把maven-resources-plugin的编码配成了 UTF-8,如果.properties文件里有中文,Properties.load()依然可能出问题。正确的做法有两种:

方案A:用 Unicode 转义

把.properties文件里的所有非 ASCII 字符转成\uXXXX形式。比如"中文"转成\u4e2d\u6587。IDEA 和 Eclipse 都有插件可以自动做这个转换。

方案B:用 XML 格式的 properties

Java 支持 XML 格式的 properties 文件,这种格式可以显式声明编码:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <entry key="app.name">我的应用</entry> </properties>

然后用Properties.loadFromXML()来读取。这种方式没有编码歧义,但可读性稍差。

方案C:在代码里指定编码读取

如果你能控制读取 properties 的代码,可以用InputStreamReader显式指定 UTF-8:

Properties props = new Properties(); try (InputStreamReader reader = new InputStreamReader( new FileInputStream("config.properties"), StandardCharsets.UTF_8)) { props.load(reader); }

这种方式最灵活,但需要改代码,适合新项目。

提示:很多Input length = 1的案例,根源就是.properties文件里写了中文,而项目编码配置和文件实际编码不一致。优先检查所有.properties文件。

4.3 策略三:清理 BOM 和不可见字符

如果确认是 BOM 或零宽字符导致的问题,需要清理这些"脏字节"。清理 BOM 可以用sed:

# 清理 UTF-8 BOM sed -i '1s/^\xEF\xBB\xBF//' src/main/resources/config.properties

批量清理:

find src/main/resources -type f -exec sed -i '1s/^\xEF\xBB\xBF//' {} \;

清理零宽字符可以用perl:

perl -i -pe 's/\x{e2}\x{80}\x{8b}//g; s/\x{e2}\x{80}\x{8c}//g' src/main/resources/config.properties

在 Windows 上,可以用 Notepad++ 的"显示所有字符"功能查看不可见字符,然后手动删除。或者用 PowerShell:

$content = [System.IO.File]::ReadAllText("config.properties") $content = $content -replace "[\u200B-\u200D\uFEFF]", "" [System.IO.File]::WriteAllText("config.properties", $content, [System.Text.Encoding]::UTF8)

清理完之后,建议用xxd再检查一遍文件头,确认 BOM 已经没了。

4.4 策略四:跳过不必要的资源处理

有时候,问题文件其实不需要被maven-resources-plugin处理。比如某些二进制文件、图片、字体文件,它们本来就不是文本,不应该被编码转换。这时候可以通过includes和excludes配置来跳过它们:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.2.0</version> <configuration> <encoding>UTF-8</encoding> <nonFilteredFileExtensions> <nonFilteredFileExtension>jpg</nonFilteredFileExtension> <nonFilteredFileExtension>png</nonFilteredFileExtension> <nonFilteredFileExtension>gif</nonFilteredFileExtension> <nonFilteredFileExtension>pdf</nonFilteredFileExtension> <nonFilteredFileExtension>zip</nonFilteredFileExtension> </nonFilteredFileExtensions> </configuration> </plugin>

nonFilteredFileExtensions告诉插件:这些扩展名的文件不要做 filtering,直接二进制拷贝。这样就能避免插件去"读"这些文件的内容,自然也不会触发编码错误。

如果你确定某个目录下的文件完全不需要处理,也可以用excludes:

<configuration> <excludes> <exclude>**/*.bin</exclude> <exclude>templates/**</exclude> </excludes> </configuration>

但要注意,excludes掉的文件不会被复制到target/classes,如果运行时需要这些文件,就不能排除。

5. 几个容易踩的坑和实测经验

5.1 改了 pom.xml 但问题依旧:缓存和 IDE 的干扰

我遇到过好几次这样的情况:明明在pom.xml里配好了 UTF-8,文件也用iconv转过了,但mvn clean package还是报Input length = 1。后来发现是 IDE 的缓存或者 Maven 的本地仓库在捣乱。

第一个坑:IDE 的编码设置覆盖了 Maven 配置。IntelliJ IDEA 有自己的文件编码设置(Settings → Editor → File Encodings),如果这里配的是 GBK,而文件实际是 UTF-8,IDE 在保存文件时可能会"自作主张"地转换编码。解决办法是把 IDE 的编码也统一成 UTF-8,并且勾选"Transparent native-to-ascii conversion"(针对 properties 文件)。

第二个坑:Maven 本地仓库里的旧插件缓存。有时候maven-resources-plugin的旧版本被缓存了,即使你改了pom.xml里的版本号,Maven 还是用旧的。解决办法是删掉本地仓库里对应的插件目录:

rm -rf ~/.m2/repository/org/apache/maven/plugins/maven-resources-plugin/

然后重新构建,让 Maven 重新下载。

第三个坑:target目录没清理。有时候target/classes里残留了上一次构建的旧文件,导致问题看起来"阴魂不散"。养成习惯:改完配置先mvn clean,再mvn package。

5.2 多模块项目里的编码传递问题

在多模块 Maven 项目里,父pom.xml里配的project.build.sourceEncoding不一定会自动传递给所有子模块。特别是当子模块有自己的<properties>覆盖时,编码配置可能就丢了。

我的做法是在父pom.xml里用<pluginManagement>统一管理maven-resources-plugin的配置:

<build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.2.0</version> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </pluginManagement> </build>

然后在每个子模块里只需要声明插件(不需要重复配置):

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> </plugin> </plugins> </build>

这样能保证所有子模块用同一套编码配置,避免"这个模块好、那个模块坏"的诡异现象。

5.3 从 GBK 迁移到 UTF-8 的完整操作清单

如果你的项目是从 GBK 迁移到 UTF-8,下面这份清单可以帮你少走弯路:

步骤操作验证方式
1备份整个项目确认备份目录存在
2修改父 pom 的 sourceEncoding 为 UTF-8mvn help:effective-pom查看
3用 iconv 批量转换资源文件file -i确认编码
4清理所有 BOMgrep -rl $'\xEF\xBB\xBF'无输出
5转换 Java 源码文件IDE 批量转换或 iconv
6清理 target 和本地仓库缓存mvn clean
7全量构建验证mvn clean package成功
8运行测试确认功能正常测试用例全部通过

这份清单看起来简单,但每一步都有细节。比如第3步,iconv转换时如果遇到无法转换的字符,会直接报错退出。这时候需要加//IGNORE参数忽略无法转换的字符,但忽略之后要人工检查是否有内容丢失。

第5步转换 Java 源码时,要注意源码里的中文字符串常量。如果源码是 GBK,直接转 UTF-8 后,字符串常量会变成乱码。正确的做法是先用 IDE 的"Convert encoding"功能,它会同时处理文件编码和字符串内容。

5.4 一个真实的排查案例

之前有个项目,mvn clean package在本地开发机上一直报Input length = 1,但同事的机器上却能正常构建。我们对比了两边的环境,JDK 版本、Maven 版本、pom.xml都一模一样。

最后发现问题出在一个.properties文件上。这个文件里有一行注释,是某位同事从网页上复制配置项时带进来的,注释末尾有一个零宽空格。在同事的机器上,这个零宽空格恰好被某个编辑器"吃掉"了,所以构建正常。而在我的机器上,文件是从 Git 仓库直接拉下来的,零宽空格原样保留,就触发了编码错误。

用xxd查看那个文件,在注释行末尾看到了e2 80 8b三个字节。删掉之后,构建立刻恢复正常。

这个案例给我的教训是:不要相信"在我机器上是好的"这种话。编码问题往往和文件在传输、复制、保存过程中的细微差异有关。遇到这类问题,第一件事是用xxd看原始字节,而不是靠肉眼观察。

6. 预防措施:让这个报错不再出现

6.1 在 CI 流程里加入编码检查

与其等构建时报错,不如在代码提交阶段就发现问题。可以在 CI 流程里加一个编码检查步骤:

#!/bin/bash # 检查所有资源文件是否为合法 UTF-8 find src/main/resources -type f \( -name "*.properties" -o -name "*.xml" -o -name "*.json" -o -name "*.yml" \) | while read file; do if ! iconv -f UTF-8 -t UTF-8 "$file" -o /dev/null 2>/dev/null; then echo "编码错误: $file" exit 1 fi done echo "所有资源文件编码检查通过"

这个脚本会遍历所有文本资源文件,用iconv验证它们是否是合法 UTF-8。如果有问题,CI 会直接失败,阻止有问题的代码合并。

还可以加上 BOM 检查:

if grep -rl $'\xEF\xBB\xBF' src/main/resources/; then echo "发现 BOM 文件,请清理" exit 1 fi

6.2 统一团队的文件保存规范

编码问题的根源往往是"人"的问题,而不是"技术"的问题。不同的人用不同的编辑器,保存文件时用了不同的编码,问题就来了。

我的建议是在项目根目录放一个.editorconfig文件:

root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{properties,xml,json,yml,yaml}] charset = utf-8

.editorconfig被主流 IDE 和编辑器支持,能强制统一编码、换行符、缩进等格式。虽然它不能100%杜绝问题,但能大幅降低"每个人保存的文件都不一样"的概率。

另外,在项目的 README 或者开发规范文档里明确写一句:"所有文本文件必须使用 UTF-8 无 BOM 编码保存。"这句话看起来是废话,但真到了出问题的时候,它能帮你省下几个小时的排查时间。

6.3 升级 maven-resources-plugin 的版本

maven-resources-plugin3.2.0 是2020年发布的版本,虽然不算老,但后续版本在错误提示方面有所改进。比如 3.3.0 之后的版本,在遇到编码错误时会更明确地指出是哪个文件出了问题。

如果你的项目没有特殊原因锁定在 3.2.0,可以考虑升级到更新的版本:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.3.1</version> </plugin>

升级前建议先在小范围测试,确认没有兼容性问题。大多数情况下,maven-resources-plugin的版本升级是无痛的,因为它做的事情很单一。

6.4 给 properties 文件加一道保险

对于.properties文件,我习惯在项目里加一个自定义的编码检查工具类,在应用启动时扫描所有 properties 文件,确认它们能被正确加载。这样即使构建阶段漏过了问题,启动阶段也能快速发现。

public class PropertiesEncodingChecker { public static void checkAll(Path resourceDir) throws IOException { Files.walk(resourceDir) .filter(p -> p.toString().endsWith(".properties")) .forEach(p -> { try (InputStreamReader reader = new InputStreamReader( Files.newInputStream(p), StandardCharsets.UTF_8)) { new Properties().load(reader); } catch (IOException e) { throw new RuntimeException("Properties 文件编码错误: " + p, e); } }); } }

这个工具类可以在单元测试里调用,确保所有 properties 文件都是合法 UTF-8。虽然多写了几行代码,但比起在生产环境发现配置乱码,这点投入完全值得。

7. 写在最后:编码问题没有捷径

Input length = 1这个报错,说到底是一个"信息不足"的问题。Maven 没有告诉你哪个文件出了问题,你只能靠自己的排查手段去定位。我见过有人因为这个报错重装了 JDK,有人换了 Maven 版本,有人把整个项目重新 clone 了一遍——这些操作偶尔能"碰巧"解决问题,但大多数时候只是浪费时间。

真正有效的做法是:先定位文件,再分析字节,最后统一编码。这三步走下来,90%以上的编码问题都能解决。剩下的10%,通常是文件里混入了极其隐蔽的不可见字符,需要用xxd逐字节检查。

我在实际项目里养成了一个习惯:每次新建资源文件后,先用file -i确认编码,再用xxd看一眼文件头有没有 BOM。这个习惯花不了几秒钟,但能避免后面几个小时的排查。编码问题就是这样,预防的成本远低于修复的成本。

如果你现在正被这个报错卡住,不妨按这篇文章的排查链路走一遍。先从mvn process-resources缩小范围,再用二分法锁定文件,然后用iconv或xxd分析字节,最后统一编码配置。整个过程可能需要半小时到一小时,但比起盲目尝试各种"偏方",这条路是最稳的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进