Maven 这东西,装起来确实不复杂,解压、配两个环境变量、敲一条命令验证,前后也就十分钟的事。但我在带新人做项目的时候发现一个挺普遍的现象:十个人里九个"装上了",可能正常跑起来的不到一半。常见状态是本地仓库默认塞在用户目录里,C 盘一天天见红;镜像源没配,拉一个 Spring 依赖卡上二十分钟;构建日志里刷出一堆blocked mirror for repositories的警告,新手完全看不懂这是什么东西,只能关掉终端假装没看见。这些都不是安装失败,但都属于"装了个寂寞"。这篇我就按自己在一线项目里的习惯,把 Maven 3.8.8 从下载到配置走一遍完整流程,中间该解释的为什么、该提前踩掉的坑、该抄的配置,都写清楚。适合刚上手 Java 构建工具的新人,也适合那些用了几年的老项目、至今还在靠 IDE 内置 Maven 硬撑的同学。
1. 装之前先想明白:为什么我不建议直接用 IDE 内置的 Maven
1.1 内置 Maven 的三个隐性成本
主流 Java IDE 基本都会自带一份 Maven,开箱就能跑,很多人因此觉得没必要单独装。我早年也这么想,直到遇到一次团队协作事故才改了主意。内置版本最大的问题是版本不可控,IDE 升级一次,底层 Maven 从 3.6 跳到 3.9,构建行为跟着变,但你的pom.xml还是老的,结果就是本地能跑、流水线挂掉,排查方向还容易跑偏到代码上。
第二个成本是环境割裂。IDE 里的 Maven 用的是它自己的配置路径,命令行mvn用的是另一套,两边本地仓库、镜像源、JDK 都可能不一样。我自己就遇到过一次,命令行打包正常,IDE 里点构建就报找不到依赖,查了半天发现是两个 Maven 读了不同的settings.xml。这种问题特别耗人心气,因为报错信息完全指向不了真正原因。
第三个是脚本能力缺失。项目里那些批量清理、依赖树分析、多模块选择性构建的脚本,本质上都是mvn命令的组合。你不可能指望每个 CI 步骤都去点 IDE 按钮。所以独立装一份、配好环境变量、让命令行能直接调用,这件事在真实项目里是刚需,不是洁癖。
1.2 Maven 3.8.8 这条版本线卡在哪
3.8.8 是 3.8 系列后期的维护版本,它处在一个挺微妙的位置。往上,3.9 系列提高了 JDK 门槛,一些还在跑老 JDK 的遗留项目升不上去;往下,3.6 系列又太旧,缺了不少安全修复。3.8.8 正好卡在"够新但门槛不高"这个区间,所以很多企业的构建基线会选它。
这条版本线最需要提前知道的一件事,是从 3.8.1 开始默认阻塞所有外部 HTTP 仓库。原因不复杂:明文的 HTTP 传输有被中间人篡改的风险,构建产物被替换是供应链层面的严重问题,所以官方直接在默认配置里内置了一条镜像规则,把所有http://开头的外部仓库重定向到一个不存在的地址http://0.0.0.0/。你在settings.xml里能看到类似这样一段:
<mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:http:*</mirrorOf> <name>Pseudo repository to mirror external repositories initially using HTTP.</name> <url>http://0.0.0.0/</url> <blocked>true</blocked> </mirror>这段配置是内置的,不用你写,但它会实实在在影响你。如果你们的私服或者某个第三方仓库还是 HTTP 的,构建就会直接报blocked mirror for repositories,然后依赖拉不下来。新手看到这个报错大概率会懵,因为它不像"找不到依赖"那么直白。所以我在安装阶段就把这件事讲清楚,后面配镜像的时候一并处理掉。
2. 环境自检:两件必须先确认的事
2.1 JDK 版本和 Maven 版本的对应关系
装 Maven 之前先看 JDK,这是我固定的第一步。Maven 本身是个 Java 程序,启动靠的就是JAVA_HOME指向的那套 JDK,版本不匹配的话,报错信息往往特别隐晦。下面这张表是我整理的各版本门槛,做项目选型时可以直接对照:
| Maven 版本 | 最低 JDK 要求 | 典型适用场景 |
|---|---|---|
| 3.6.x | Java 7 | 极老的遗留系统,几乎不再新建 |
| 3.8.x | Java 7 | 仍在维护的老项目、需要兼容低版本 JDK 的场景 |
| 3.9.x | Java 8 | 新项目、希望获得更好依赖解析行为的场景 |
3.8.8 最低支持 Java 7,这个特性对还在维护老系统的团队挺关键。但要注意,Maven 能跑起来是一回事,你的项目能不能编译是另一回事。pom.xml里的maven.compiler.source和target决定的是编译产物级别,两者别混为一谈。我见过有人把JAVA_HOME指到 JDK 8,但项目要编译出 Java 17 的字节码,结果一路UnsupportedClassVersionError,还以为是 Maven 装坏了。
确认方式很简单,命令行里敲java -version和echo %JAVA_HOME%(Linux 下是echo $JAVA_HOME)。如果JAVA_HOME是空的,或者指向的是一个 JRE 而不是 JDK,那就先把它修好再往下走。Maven 编译阶段会调用javac,只有 JRE 是没法编译的,但很多打包好的运行时环境里只有 JRE,这个坑特别隐蔽。
注意:
JAVA_HOME路径里不要带空格和中文,Windows 下C:\Program Files\Java\...这种路径虽然大多数情况能跑,但偶尔会在某些插件的脚本调用里出问题。稳妥做法是装到D:\dev\jdk8这类短路径下。
2.2 安装目录与磁盘规划
目录规划这件事听起来像废话,但它决定了你后面几年会不会反复折腾。我给自己和团队定的规矩是这样:
- Maven 程序本体放在统一的开发工具目录,比如
D:\dev\apache-maven-3.8.8,路径里不要有空格; - 本地仓库单独放一个大盘,比如
D:\m2repo,不要用默认的用户目录; - 配置文件用用户级的
~/.m2/settings.xml,不动安装目录里的conf/settings.xml。
为什么本地仓库要单独放?默认路径是${user.home}/.m2/repository,一个中等规模的多模块项目,拉全依赖轻松过 1.5 GB,跑几年下来加上各种快照版本,5 GB 到 10 GB 都很正常。放在 C 盘用户目录下,早晚会遇到磁盘告警,而且重装系统时这份仓库大概率跟着一起没了,重建一遍又要重新拉几十分钟。挪到独立盘符,重装系统后直接指过去,一秒恢复。
为什么不改安装目录里的conf/settings.xml?因为这个文件属于 Maven 程序本体,你哪天想试试 3.9 系列,解压一个新的,配置全丢,又得重来一遍。用户级配置在~/.m2/settings.xml,跟 Maven 版本解耦,换版本、装多版本都不用重新配。这个习惯我坚持了很多年,收益非常明显。
3. 拿安装包:从下载到校验
3.1 二进制包和源码包的区别
官方发布页上每个版本一般会给出几种包,名字很像,选错了会浪费半天时间。区分方式看后缀:
apache-maven-3.8.8-bin.tar.gz和-bin.zip:二进制发行包,解压即用,这是你要的;apache-maven-3.8.8-src.tar.gz和-src.zip:源码包,里面是 Maven 自己的 Java 源码,编译它需要额外的一堆工具,普通使用者完全不需要;- 还有一类带
-bin但附带了-sha512、.asc的校验文件,是给你验证完整性用的。
选bin.zip还是bin.tar.gz只看你的解压工具,内容完全一样,压缩格式不同而已。Windows 下用 zip 方便,Linux 下一般用 tar.gz。别去下 src 包,我见过有人在 src 包里找了半天bin目录,最后怀疑是下载源有问题,其实就是拿错了。
3.2 校验和与完整性验证
这一步很多人跳过,但我觉得值得养成习惯,尤其是包要从多个渠道传递的时候。校验的思路很简单:官方会给出该文件的哈希值,你在本地算一遍,两个值对上就说明文件在传输过程中没被改动。
Windows 下用 PowerShell:
Get-FileHash .\apache-maven-3.8.8-bin.zip -Algorithm SHA512Linux 下更直接:
sha512sum apache-maven-3.8.8-bin.zip拿输出的哈希跟发布页上的值逐字符比对,一致再解压。这一步花不了两分钟,但能挡掉一类很难查的问题:包不完整导致解压后少了某个 jar,最后报的错跟 Maven 本身毫无关系,你会往完全错误的方向排查。
解压之后目录结构是这样的,值得先看一眼熟悉一下:
apache-maven-3.8.8/ ├── bin/ → mvn、mvn.cmd 启动脚本 ├── boot/ → plexus-classworlds 类加载器 ├── conf/ → settings.xml 全局配置、logging 配置 └── lib/ → Maven 自己的 jarbin目录是待会儿要加到PATH里的,conf/settings.xml是全局配置模板。真正要改的是用户级配置,但从conf里复制一份模板过去是最省事的起点,因为里面注释齐全,能当文档看。
4. 环境变量配置:Windows 与 Linux 两条路径
4.1 Windows 下的配置流程
Windows 上的关键点是用MAVEN_HOME还是M2_HOME。两个名字历史上都存在过,老教程里M2_HOME出现频率很高,但新版启动脚本已经以MAVEN_HOME为准,3.9 系列更是直接不再识别M2_HOME。所以从现在开始统一用MAVEN_HOME,别给自己埋雷。
具体操作我习惯走图形界面,不容易出错:
- 右键"此电脑"→ 属性 → 高级系统设置 → 环境变量;
- 在"系统变量"里新建
MAVEN_HOME,值填解压后的根目录,比如D:\dev\apache-maven-3.8.8,不要带\bin; - 找到
Path,编辑,新增一行%MAVEN_HOME%\bin; - 一路确定保存,然后关掉所有已经打开的终端窗口,重新开一个。
最后这一步是最容易翻车的。环境变量的修改只对之后新启动的进程生效,已经开着的 cmd 或 PowerShell 读的还是旧的环境块,你敲mvn -v会一直报"不是内部或外部命令",然后开始怀疑人生。我见过有人为此重装了两次系统,其实就是没重开终端。
还有一个细节:Path里如果以前配过别的 Maven 的bin目录,记得删掉或者调整顺序,否则可能调用到老版本。Windows 的Path是按顺序查找的,先命中的生效。
4.2 Linux 与 macOS 下的配置流程
Linux 下我一般不用系统级配置,而是在用户的 shell 配置文件里写,~/.bashrc或者~/.zshrc,看用的是哪个 shell。这样不影响机器上的其他用户,也不会因为系统升级被覆盖掉。
export MAVEN_HOME=/opt/apache-maven-3.8.8 export PATH=$MAVEN_HOME/bin:$PATHPATH的拼接顺序有讲究,把$MAVEN_HOME/bin放在前面,能保证优先命中你自己装的这份。写完别忘了source ~/.bashrc让它立即生效,或者干脆退出重登。
macOS 从 Catalina 之后默认 shell 换成了 zsh,配置文件是~/.zshrc。如果你照着老教程改了~/.bash_profile然后发现不生效,就是这个原因。判断当前 shell 用echo $SHELL,一目了然。
还有一种做法是往/etc/profile.d/下放一个maven.sh,对所有用户生效。企业里的构建机器适合这么做,但个人开发机我建议还是用户级,出问题好回退。
4.3 验证安装是否真的生效
配置完,敲这一条:
mvn -v正常输出大概长这样:
Apache Maven 3.8.8 (4d8b0b96f...) Maven home: D:\dev\apache-maven-3.8.8 Java version: 1.8.0_371, vendor: Oracle Corporation Java home: D:\dev\jdk8\jre Default locale: zh_CN, platform encoding: GBK OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"这段输出里的每一行都值得看,尤其是三处:
Maven home是不是你刚配的那个路径。如果指向了别的地方,说明Path里有旧的bin抢先命中了。
Java home是不是你想要的那套 JDK。如果这里显示的是某个 JRE,或者是一个你没预期的 JDK,那说明JAVA_HOME有问题。注意 Maven 3.8.8 在部分环境下会显示jre子目录,这是正常的,它内部会往上找。
platform encoding是不是GBK。这个在中文 Windows 上很常见,它会导致构建时中文注释或资源文件乱码。后面配置里可以用-Dfile.encoding=UTF-8或者MAVEN_OPTS修正。
提示:
mvn -v只验证了 Maven 能启动,不代表整套链路通了。真正要验证得跑一次完整生命周期,找个空目录建个最小pom.xml,执行mvn archetype:generate或者干脆mvn help:system,能看到它去远程仓库拉东西并且成功,才算真的通了。
mvn help:system是个我很推荐的自检命令,它会把系统属性、环境变量、本地仓库路径全打出来。等于一次性确认了 Java 版本、操作系统信息、仓库位置三件事,比一条条去看高效得多。第一次跑它会下载几个插件,正好能验证网络和镜像配置通不通。
5. settings.xml:决定日常体验的三个核心配置
5.1 本地仓库路径怎么挪
把conf/settings.xml复制一份到~/.m2/settings.xml(Windows 是C:\Users\你的用户名\.m2\settings.xml),然后找到<localRepository>那一行。它默认是注释掉的,取消注释并改成你的目标路径:
<localRepository>D:/m2repo</localRepository>路径分隔符这里有个小坑:Windows 下用正斜杠/或者双反斜杠\\都行,但单个反斜杠\不行,因为 XML 里反斜杠在某些位置会被当转义。我统一用正斜杠,跨平台一致,Linux 下也不用改。
挪仓库这件事有个副作用需要提前知道:改完之后第一次构建会重新下载全部依赖,因为新目录是空的。如果你之前用默认路径跑过项目,可以考虑把旧目录整个拷过去,省一次全量下载。拷贝的时候注意.m2目录下除了repository,还有settings.xml和可能的wrapper目录,只拷repository就行。
5.2 镜像源配置与 mirrorOf 的匹配规则
镜像源是提速的关键,也是配置里最容易配错的部分。基本写法是这样:
<mirrors> <mirror> <id>internal-mirror</id> <name>Internal Public Mirror</name> <url>https://mirror.example.com/repository/maven-public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>mirrorOf这个字段决定这条镜像接管哪些仓库,取值规则值得单独说一下,因为配错了不会报错,只会静默地不生效:
| 取值 | 含义 |
|---|---|
central | 只接管中央仓库 |
* | 接管所有仓库(包括私服),慎用 |
external:* | 接管所有非 localhost、非 file 协议的外部仓库 |
*,!snapshots-repo | 接管全部,但排除 id 为 snapshots-repo 的仓库 |
repo1,repo2 | 只接管指定 id 的仓库 |
我的建议是别图省事写*。写*会把你们公司私服的请求也劫持到公共镜像去,结果就是内部快照依赖全部解析失败,报"找不到 artifact",但你在私服网页上明明能看到它。这个错误特别难查,因为报错信息里不会告诉你是镜像规则的问题。写central,或者用*,!你的私服id排除掉,稳得多。
5.3 HTTP 仓库阻塞规则与放行方式
回到第 1 节提到的 HTTP 阻塞。如果你的私服地址还是http://开头,mvn会报:
[ERROR] Failed to execute goal ... Blocked mirror for repositories: [your-repo (http://...)]处理方式分两种,按优先级推荐。
第一种,把仓库升级成 HTTPS。这是根治,也是我强烈建议的方向。内部仓库管理软件现在基本都支持 HTTPS,配一张内部证书或者用受信任证书都行,改一下url就完事。
第二种,显式放行。如果短期内确实升不上去,可以在settings.xml里覆盖内置的阻塞规则,做法是定义一条同 id 的镜像,把blocked设为false,url指向真实地址:
<mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:http:*</mirrorOf> <url>http://legacy-internal.example.com/repository/maven-public/</url> <blocked>false</blocked> </mirror>同 id 的配置会覆盖内置的那条,这样原本被拦的请求就放行了。但我要把话说在前面:这只是权宜之计,明文传输的构建在供应链安全上是有实际风险的,能升 HTTPS 就别拖。
还有一种情况是只有个别pom.xml里写了 HTTP 仓库,你不想全局放行。那就在那个项目的pom.xml里给仓库换个写法,或者在settings.xml里用mirrorOf精确排除:
<mirrorOf>*,!legacy-http-repo</mirrorOf>这样legacy-http-repo这个 id 的仓库不走镜像,直接访问。但注意,它仍然会被内置的 HTTP blocker 拦下来,所以还得配合上面的放行配置一起用。这两层规则叠加的逻辑确实绕,我建议配置完之后跑一次mvn help:effective-settings,把实际生效的合并配置打出来看一眼,比在脑子里推演靠谱。
5.4 私服认证信息放哪儿
公司私服一般需要账号密码,凭据写在settings.xml的<servers>段里:
<servers> <server> <id>internal-releases</id> <username>deployer</username> <password>******</password> </server> </servers>这里的关键是id必须和仓库配置里的id完全一致,一个字符都不能差。Maven 就是靠这个 id 去匹配凭据的,匹配不上就会拿着匿名身份去请求,然后收到 401,报错信息里通常只说"未授权",不会告诉你是 id 写错了。这个坑我踩过至少两次。
密码明文放在settings.xml里,在个人开发机上可以接受,但团队共享的机器上就不合适了。Maven 提供了加密方案,用mvn --encrypt-password生成密文,配合settings-security.xml使用。配置稍微麻烦点,但如果是构建服务器,值得做。
另外,~/.m2/settings.xml这个文件本身要注意权限。它可能包含私服凭据,别顺手提交到代码仓库里。我见过有团队把它放进项目目录然后一起提交了,这属于安全事故级别的疏忽。
6. 常见问题排查实录
6.1 高频报错速查表
下面这些是我这些年被问得最多、自己也踩过的问题,整理成表方便对着查:
| 报错现象 | 大概率原因 | 处理方式 |
|---|---|---|
'mvn' 不是内部或外部命令 | PATH没配或终端没重开 | 检查MAVEN_HOME是否带\bin,重开终端 |
JAVA_HOME not found | 环境变量缺失或拼写错误 | 确认变量名全大写、路径存在 |
UnsupportedClassVersionError | Maven 和 JDK 版本不匹配 | 对照第 2.1 节的表,调整JAVA_HOME |
Blocked mirror for repositories | 仓库是 HTTP 协议被拦截 | 升 HTTPS,或按 5.3 节放行 |
| 依赖下载卡住不动 | 镜像源不通或网络受限 | 换镜像,用-X看实际请求地址 |
401 Unauthorized | server的id和仓库不匹配 | 逐字符核对两处id |
Could not resolve dependencies | 镜像mirrorOf把私服劫持了 | 改mirrorOf,排除私服 id |
| 构建时中文乱码 | platform encoding是 GBK | 设MAVEN_OPTS=-Dfile.encoding=UTF-8 |
构建OutOfMemoryError | 默认堆太小 | 调MAVEN_OPTS=-Xmx1024m |
6.2 几个只有踩过才知道的坑
坑一:MAVEN_OPTS和JAVA_TOOL_OPTIONS的区别。前者只影响mvn进程本身,后者会影响这台机器上所有 JVM 进程。调内存参数用MAVEN_OPTS就行,别动JAVA_TOOL_OPTIONS,否则可能把别的服务搞出奇怪行为。我见过有人在全局设了JAVA_TOOL_OPTIONS=-Xmx256m,结果同一台机器上的测试服务集体 OOM,查了很久才发现是环境变量的锅。
坑二:IDE 里配置没生效。手工装完 Maven 后,IDE 里要显式指向你装的这一份,路径是设置里的构建工具那一栏。有三个地方要一起改:Maven 主目录、用户设置文件(settings.xml)、本地仓库。改完还要注意"覆盖"勾选项,不勾的话 IDE 还是用默认值。有次我改完主页目录忘了改 settings 路径,结果 IDE 用的是内置配置,本地仓库跟我命令行下的完全不是同一个,依赖下载了两遍,磁盘直接多了 3 GB。
坑三:本地仓库里的_remote.repositories文件。Maven 会在每个 artifact 目录下写一个小文件,记录这个依赖是从哪个仓库下来的。当你换镜像源之后,有时候会遇到"依赖明明在本地仓库里,构建还说找不到"。原因就是这个小文件里记的仓库 id 跟当前配置对不上,Maven 认为这份缓存不可信。解决办法是删掉对应目录下的_remote.repositories,或者干脆加-U强制更新。批量清理可以用:
find D:/m2repo -name "_remote.repositories" -delete坑四:网络代理相关的配置残留。如果你之前配过settings.xml里的<proxies>段,换了网络环境之后很可能连不上仓库,但报错是连接超时,完全看不出跟代理有关。排查时先用-X打开调试日志,看它实际连的是哪个地址、走的什么通道。
坑五:只用-U解决不了所有问题。-U是强制更新快照和发布版本,但它只对配置为快照的依赖生效比较彻底。如果远程仓库本身没更新,你刷一百遍也没用。判断方法是用-U配合-X,看日志里有没有真的去请求远程地址,还是直接用了本地缓存。
7. 多版本共存与升级时的割接做法
7.1 同一台机器装多个 Maven
真实项目里,你很可能同时维护新老两套系统,对 Maven 版本要求不一样。让它们共存不难,核心思路是只让一个MAVEN_HOME生效,需要切换时改它。
目录上把它们并排放:
D:/dev/apache-maven-3.8.8 D:/dev/apache-maven-3.9.xPATH里只写%MAVEN_HOME%\bin,切换时改MAVEN_HOME的值,重开终端,mvn -v确认一下。这样做的好处是所有配置都在用户级settings.xml里,换版本不用重配,本地仓库也共享同一份,省磁盘。
需要频繁切换的话,可以写个小脚本,Windows 下建两个.bat,Linux 下搞两个alias。比如:
alias mvn38='MAVEN_HOME=/opt/apache-maven-3.8.8 PATH=/opt/apache-maven-3.8.8/bin:$PATH mvn'这种一次性的环境变量前缀写法很好用,只对当前这条命令生效,不会污染整个 shell 会话。
7.2 从旧版本迁移要注意的配置差异
如果你是从 3.6 系列往 3.8.8 迁,有几处配置差异必须提前处理,否则迁完直接构建失败。
HTTP 阻塞规则是新增的。3.6 时代没有任何限制,所有仓库畅通无阻,迁到 3.8.8 之后所有 HTTP 仓库立刻被拦。这是迁移时最常见的故障点,建议迁移前先扫一遍所有pom.xml和settings.xml,把所有http://找出来,能升 HTTPS 就升,不能升的提前准备好放行配置。
mirrorOf的external:*语义更严格。新版对external的定义收紧了,file://和localhost不算是 external,如果你的镜像依赖旧行为,可能会出现某些仓库不再走镜像的情况。
依赖解析的顺序更确定。新版在冲突解决上更严格,以前靠"碰运气"能解析到的版本,现在可能直接报冲突。这类问题的排查工具是mvn dependency:tree,把完整的依赖树打出来看:
mvn dependency:tree -Dverbose -Dincludes=com.example-Dincludes可以按 groupId 过滤,只关心的部分,避免输出几千行看不过来。
插件版本锁定更必要。新版对插件版本的解析更倾向于用最新,跨环境构建时容易出现"我本地能跑、流水线不行"。彻底的办法是在pom.xml的<build><pluginManagement>里显式锁定所有插件的版本,别依赖默认推导。这件事我建议所有要上流水线的项目都做,收益远超投入。
迁移的验证方式我有个固定套路:先在一个非核心模块上试,跑通clean package和dependency:tree,再把它推到流水线上跑一遍,观察构建时长和产物差异,最后才动主分支。直接全量切过去,出问题时的回退成本会高很多。
装 Maven 这件事本身没有多少技术含量,真正拉开差距的是配置阶段的那些选择:仓库放哪儿、镜像怎么匹配、HTTP 怎么处理、多版本怎么共存。这些决定不会让你当天就感受到差别,但会在接下来一两年的每一次构建里持续发挥作用。我自己最深的体会是,凡是"反正能用就行"的地方,最后都会在某个意想不到的时刻变成排查两小时的问题源头,反而是前期多花二十分钟把配置写清楚,后面省下的是成倍的时间。