升级 Maven 版本这事儿,平时没人会主动去做,但只要哪天你动了“顺便把 Maven 升到最新版”的念头,或者换了新电脑装了最新发行版,再跑一个老项目,大概率会迎面撞上这么一道红色报错:Blocked mirror for repositories。我第一次见到这个报错时,第一反应是“我 settings.xml 写错了吧”,于是开始各种检查 mirror 配置、换镜像地址、清本地仓库.m2/repository,折腾了大半天才搞清楚:问题根本不在我写的配置本身,而是 Maven 3.8.1 之后悄悄内置了一套“HTTP 仓库拦截机制”,一块名为blocked的隐藏标签把路给封死了。
这篇内容,就把我当时从摸不着头脑到彻底解决的完整经过写出来,包括这个报错背后的设计逻辑、不同场景下的解决方案、可以直接抄作业的配置模板,以及几个我实测踩过的坑。如果你刚升级 Maven 就翻车,或者正在被公司内网 HTTP 仓库折磨,这篇应该能帮你省下至少半天排查时间。
1. 先把报错现场还原:什么样的配置最容易触发 Blocked mirror
1.1 典型翻车现场:从 3.6.3 升到 3.8.6 之后
先说一个我实际遇到过的案例,很有代表性。某开发机之前用的是 Maven 3.6.3,settings.xml里配了一个国内公共镜像,mirrorOf填的是central,镜像 URL 是老式的http://开头地址。这个组合在 3.6.3 时代一直跑得好好的,依赖拉取速度也还行,没人动过它。
后来因为某个插件需要新版 Maven,我把本机 Maven 升到了 3.8.6。结果执行mvn clean package时,控制台直接打出一片红:
[ERROR] Failed to execute goal on project demo: Could not resolve dependencies [ERROR] Failed to collect dependencies at io.example:common-utils:jar:2.4.1 [ERROR] Could not transfer artifact io.example:common-utils:pom:2.4.1 from/to central [ERROR] Blocked mirror: https://repo.maven.apache.org/maven2看到Blocked mirror: https://repo.maven.apache.org/maven2这行,我当时的第一反应是“这是什么鬼?中央仓库明明是 HTTPS 地址,怎么会被 block?”问题就出在这个“明明是 HTTPS”上——它确实是 HTTPS,可 Maven 真正拦截的并不是这个地址本身,而是我 settings.xml 里那个http://开头的旧镜像。Maven 在解析中央仓库请求时,发现请求应该转发给我配的镜像,但我的镜像 URL 是 HTTP,且匹配了内置拦截规则,于是请求还没出站就被掐断了。报错信息准备回退到中央仓库真实地址继续尝试,结果发现中央仓库也同样被全局拦截规则覆盖,于是干脆抛出了Blocked mirror。
这个案例的典型特征就是:升级了 Maven 的 minor 版本,但 settings.xml 里的镜像地址还停留在 HTTP 时代。如果你也符合这个特征,基本就是同款问题。
1.2 报错信息的四个关键字段,分别代表什么
Blocked mirror这条报错看起来简短,但里面其实藏了好几个关键信息。我这里拆开逐项讲:
[ERROR] Blocked mirror for repositories: [ERROR] central (https://repo.maven.apache.org/maven2, default, releases+snapshots)central:仓库 ID。这是 Maven 超级 POM 内置的中央仓库标识,也是绝大多数依赖请求默认指向的仓库。https://repo.maven.apache.org/maven2:仓库的实际 URL。这里的 URL 显示的是中央仓库官方地址,并不一定是你真正去访问的地址。当 Maven 的 mirror 机制介入后,实际下载地址可能是另一个,但如果镜像被拦截,报错里通常会回退显示原始仓库定义。default:仓库的 layout 类型,default表示标准的 Maven 2 布局,不用管。releases+snapshots:这个仓库允许解析 release 版本和 snapshot 版本。如果只允许 release,那 snapshot 依赖会从其他仓库找。
看到这行报错时,重点不是读仓库 URL,而是要意识到:有一个 mirror 配置匹配到了central,而且这个 mirror 触发了拦截规则。所以排查方向应该是去settings.xml里看 mirror 配置,而不是盯着报错里的中央仓库地址发呆。
另外,还有一个常见的报错变体,会把被拦截的地址直接亮出来:
[ERROR] Blocked mirror: http://maven.example-repo.com/repository/public/这种报错更直白,它直接把那个http://开头的镜像地址写出来了,说明这个镜像本身就是一个 HTTP 地址,被内置拦截器一票否决。
到这里,现象部分就清楚了。下一步我们要搞清楚 Maven 3.8+ 到底是基于什么规则执行拦截的。
2. 幕后机制拆解:Maven 3.8+ 凭什么“拦”你的镜像
2.1 藏在默认配置里的 maven-default-http-blocker
Maven 3.8.1 做了一件很多人当时没注意的事:在默认的超级 POM 里增加了一个 mirror 配置,名字叫maven-default-http-blocker。它的完整定义如下:
<mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:http:*</mirrorOf> <url>http://0.0.0.0/</url> <blocked>true</blocked> </mirror>这哥们儿的职责非常简单粗暴:只要你的仓库地址是 HTTP 协议,而且不在本机 localhost 范围内,Maven 就拒绝访问。默认的blocked值直接给你设成true,等于提前在所有 HTTP 仓库前面立了一块“此路不通”的告示牌。
问题在于,这个配置是默认的,很多开发者根本不知道它的存在。大家只知道自己写了 settings.xml 里那个http://镜像,怎么突然就不能用了。实际上就是默认配置和自己的配置在合并之后,默认配置里的拦截器先把请求挡了下来。
这里要补充一个背景:Maven 为什么要在 3.8.1 这个时间点干这件事?因为那时候软件供应链安全逐渐成为共识,HTTP 明文传输依赖包的后果很严重——你在网上下载的 jar 包,中间任何一跳网络设备都可能篡改内容,而且你不会收到任何警告。很多知名仓库都在推进“HTTPS Only”的迁移,Maven 团队也顺势在客户端这边加了一把锁:想用 HTTP 仓库?要么明确覆盖默认策略,要么就别用。
2.2 external:http:* 到底匹配了什么
mirrorOf的值是external:http:*,这个写法看着像正则,但它是 Maven 特有的通配语法。拆开理解:
external:前缀表示“非本机地址”。本机地址包括localhost、127.0.0.1、::1,如果你的仓库地址恰好是本机启动的服务,就不会被这个前缀匹配。http:表示协议限定为 HTTP,HTTPS 不受影响。*是通配符,表示匹配所有符合前面条件的主机和路径。
合在一起,external:http:*的含义就是:所有不是 localhost 的、以 http:// 开头的外部仓库,全部命中拦截。所以一个地址如果是http://localhost:8081/repository/maven-public/,它不会匹配这个规则,可以正常使用。但如果是http://192.168.1.10:8081/repository/maven-public/或者http://maven.example-repo.com/repository/public/,那就直接命中。
这套设计对大多数开发者是安全的,因为日常开发用的公共镜像、公司内网仓库,如果走 HTTP,要么迁移到 HTTPS,要么就得显式覆盖规则。Maven 团队的目的就是逼所有仓库服务方往 HTTPS 走,而不是让用户在客户端做各种危险的回退。
2.3 blocked 标签的设计初衷与误伤场景
blocked这个标签是 Maven 3.8.1 引入的 mirror 新属性,官方文档里的定义很简短:true表示该 mirror 被禁用,Maven 匹配到这个 mirror 时不会真正使用它去下载,而是直接抛“Blocked mirror”错误。
设计初衷很明确:当 Maven 遇到一个被标记为 blocked 的 mirror 时,与其假装它能用然后让用户等十分钟超时,不如立刻报错,让用户意识到“这个源不行”。这个思路是好的,但它带来了一些误伤场景:
- 存量用户升级 Maven 版本后,镜像地址还是老 HTTP 地址,直接被新规则拦截。
- 公司内网部署的是老旧的 HTTP 私服,一时半会儿升级不了 HTTPS,开发环境被迫卡住。
- 有些镜像站虽然支持 HTTPS,但用户记错了,写成了 HTTP,也一样中招。
我在网上看到有不少人试图通过删除.m2目录、更换依赖版本、甚至重装 IDE 来解决这个问题,方向完全跑偏。正确思路只有两个:要么把地址改成 HTTPS,要么正面覆盖默认的封锁策略。
3. 亲测有效的 4 种解法:从改配置到覆盖拦截器
3.1 解法一:把镜像 URL 升级为 HTTPS(最推荐)
这是最干净、最符合官方预期的方案。做法很简单,打开settings.xml,找到你的 mirror 配置,把<url>标签里的http://改成https://。
实际操作时要注意,不是所有老地址都能直接改成 HTTPS 就继续用。有些镜像服务商迁移域名之后,旧地址的 HTTPS 端口已经不再提供。这时候你需要去对应镜像站的官方文档里,重新找一个支持 HTTPS 的新地址。
我自己处理那个翻车案例时,就是用新地址替换了旧地址,替换完再执行一次mvn clean package,一切恢复如初。改动虽然只是几个字符,但效果立竿见影。
这里有个检查技巧:如果一个镜像的 HTTPS 地址不支持,你在浏览器里直接访问一下,大概率会看到证书错误或连接失败,这时候就要果断换新地址,千万别硬凑。
3.2 解法二:用同名 mirror 覆盖默认 blocker(内网必备)
如果你所在的公司内网有一个老私服,由于网络隔离或证书管理等历史原因,只能提供 HTTP 地址,那么解法一就行不通了。这时候需要主动覆盖默认的maven-default-http-blocker。
覆盖的方法是利用 Maven 配置合并的优先级规则:用户级settings.xml里的同名 mirror 会覆盖默认 POM 里的同 ID mirror。你可以在自己的 settings.xml 里定义一个相同 ID 的 mirror,并把blocked改为false:
<mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:http:*</mirrorOf> <url>http://0.0.0.0/</url> <blocked>false</blocked> </mirror>url写成什么其实无所谓,因为真正干活的不是这个 mirror,它只是用来“占住”默认配置的席位。关键在于把它从blocked=true变成blocked=false,这样默认封锁规则就失效了。
但要注意,覆盖之后,Maven 会允许访问所有外部 HTTP 仓库。这等于把官方为你加的安全锁拆掉了,风险是真实存在的。如果你是团队里的配置管理员,至少要在内部文档里注明原因和风险,别默默改了让所有人都失去安全保护。
如果你只想放开某个特定内网仓库,不想全局放开,这里有一个更精细的玩法:把 mirrorOf 改成具体仓库 ID,但这样复杂度会上升。我建议优先采用后面的解法三。
3.3 解法三:缩窄 mirrorOf 的匹配范围
很多时候,用户在自己的 settings.xml 里配了mirrorOf为*或central。*表示所有仓库都走镜像,central表示中央仓库走镜像。这两种写法在实际使用中都很常见,但也容易误伤。
比如你的镜像本身是 HTTPS 的,按理说不会触发 HTTP blocker,但如果你同时还有一个 HTTP 的私有仓库定义在 pom.xml 里,而 mirrorOf 写的是*,那个 HTTP 私有仓库也会被纳入镜像管理,进而被拦截。
这时候更合理的做法是把 mirrorOf 写精确。假设你在 pom.xml 里定义了一个私服仓库<id>my-private-repo</id>,你希望所有中央仓库请求走 HTTPS 公共镜像,而私服请求走本机 HTTP 服务,那可以把 settings.xml 里的 mirrorOf 写成central,external:*,甚至直接写!my-private-repo,*来排除私服。
下面是一个例子:
<mirror> <id>public-mirror</id> <mirrorOf>central,external:*</mirrorOf> <url>https://maven.example-repo.com/repository/public/</url> </mirror>external:*在 Maven 的 mirrorOf 语法里表示“所有外部仓库”,加上central之后,这套配置既覆盖了中央仓库,又覆盖了其他外部仓库,但不会拦截 localhost 私服。如果你的私服也是外部 IP,就把它从 mirrorOf 里排除,用,!your-repo-id语法。
这种调整需要一定的基础知识,但它是让镜像策略可控的关键。很多人图省事直接写*,后续遇到奇奇怪怪的下载问题都跟这个有关系。
3.4 解法四:直接换回 Maven 3.6.3(应急但不建议)
如果你只是临时需要跑通一个构建,没时间研究配置,那换回旧版 Maven 确实能“解决”问题。但我不建议把它当长期方案,原因有三:
- 旧版 Maven 缺少后续的 bug 修复和安全补丁。
- 团队协作时,别人用新版 Maven 构建出来的结果和你用旧版构建的,可能在插件行为上有细微差异。
- 这属于治标不治本,问题始终悬在那里,下次任何人升级到 3.8+ 都会再触发。
我见过有的团队为了一个 HTTP 私服,把统一 Maven 版本钉死在 3.6.3,一年都不升级,后来新同事入职装最新版 Maven 跑项目,当场报错,大家还得花时间解释。与其这样,不如花半小时把覆盖配置写好。
4. 一份可以直接抄的 settings.xml:公共镜像与内网仓库配置模板
4.1 公共镜像代理配置模板(含 HTTPS 强制校验)
如果你面向的是公共网络环境,需要访问中央仓库,同时希望加速依赖下载,最简单的 settings.xml 配置如下。记得把镜像地址替换为你所在网络可达的公共镜像站地址:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> <mirrors> <mirror> <id>public-mirror</id> <name>Public HTTPS Mirror</name> <url>https://maven.example-repo.com/repository/public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> <profiles> <profile> <id>default-repos</id> <repositories> <repository> <id>central</id> <url>https://repo.maven.apache.org/maven2</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile> </profiles> <activeProfiles> <activeProfile>default-repos</activeProfile> </activeProfiles> </settings>这里有一个容易忽视的细节:为什么不直接只配 mirror,还要在 profile 里重复定义 central 仓库?因为镜像机制只是把请求转发到你指定的地址,但仓库本身的策略(是否允许 snapshot、更新频率等)还是由仓库定义决定的。我习惯在 profile 里显式声明 central 仓库,确保仓库策略可控,避免某些老项目在 pom.xml 里自定义的仓库规则覆盖你的镜像策略。
另外,如果你需要同时代理多个远端仓库,可以把 mirrorOf 改成central,*,这样所有仓库请求都会统一走公共镜像。但如果公司内部还有其他特殊仓库,这个写法就会误伤,慎用。
4.2 内网私有仓库配置模板(含 blocker 覆盖)
公司内网环境通常是“外网不通,只能走私服”。这种场景下,你需要的不是公共镜像,而是把私服作为唯一仓库,同时覆盖默认的 HTTP blocker。下面这份模板我实测可以直接用:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> <mirrors> <!-- 覆盖默认的 HTTP 封锁策略,允许访问内网 HTTP 私服 --> <mirror> <id>maven-default-http-blocker</id> <mirrorOf>external:http:*</mirrorOf> <url>http://0.0.0.0/</url> <blocked>false</blocked> </mirror> <!-- 真正的私服镜像 --> <mirror> <id>internal-nexus</id> <name>Internal Nexus Mirror</name> <url>http://192.168.31.20:8081/repository/maven-public/</url> <mirrorOf>*</mirrorOf> </mirror> </mirrors> <profiles> <profile> <id>internal-repos</id> <repositories> <repository> <id>central</id> <url>http://192.168.31.20:8081/repository/maven-public/</url> </repository> </repositories> </profile> </profiles> <activeProfiles> <activeProfile>internal-repos</activeProfile> </activeProfiles> </settings>注意两个 mirror 的顺序:maven-default-http-blocker必须放在前面。当 Maven 匹配镜像时,它会按声明顺序遍历,第一个匹配到的生效。如果internal-nexus写在前面且 mirrorOf 是*,那么所有请求会直接落到内网私服,根本轮不到 blocker 做判断。但为了让覆盖逻辑更清晰,建议还是把 blocker 覆盖项写在最前面。
需要特别说明的是,<blocked>false</blocked>这个属性是 Maven 3.8.1 之后才支持的,3.6.3 不认识这个标签,会自动忽略,所以旧版 Maven 用户没必要在配置里加这个。这个配置只有在 3.8+ 环境里才有意义。
4.3 配完之后怎么验证:三个命令确认生效
配置改完,别急着直接跑构建,先用三个命令验证一下生效情况。
第一个是查看有效配置:
mvn help:effective-settings这个命令会把用户 settings.xml 和全局默认配置合并后的结果打印出来。重点看<mirrors>节点里是否有blocked标签,以及maven-default-http-blocker的定义是否符合预期。
第二个是查看有效 POM:
mvn help:effective-pom如果镜像的 mirrorOf 写得不对,这个命令能帮你发现仓库 URL 是否已经被替换。
第三个是调试模式的依赖解析:
mvn dependency:resolve -X-X会输出 Maven 的调试日志,里面会显示针对每个仓库到底选择了哪个 mirror,以及请求 URL 长什么样。如果看到日志里出现Blocked mirror字样,说明配置还没改到位。
这三个命令是我每次调镜像配置的固定流程。不要嫌麻烦,一次调好,后续能省很多时间。
5. 常见报错变体与排查记录
5.1 不同 Maven 版本下报错信息的差异
很多读者可能是在不同 Maven 版本下遇到这个问题的,报错信息多少有些差异,容易让人误判。我整理了几个常见版本的表现:
| Maven 版本 | 报错表现 | 处理建议 |
|---|---|---|
| 3.8.1 ~ 3.8.7 | 明确打印Blocked mirror: <url>,重复出现两次 | 重点查 mirror 的地址协议 |
| 3.8.8 ~ 3.9.x | 报错格式略有变化,可能显示Blocked mirror for repositories: | 排查逻辑不变,仍看 mirror 和 repository 的协议 |
| 3.9.0+ | 有些场景下会提示更详细的仓库 ID 和 mirror ID | 用mvn help:effective-settings查即可 |
版本差异导致的格式变化,本质上是同一个机制在不同 Maven 模块里的输出方式不一样,不影响排查路径。我在 3.9.2 上测试过,只要把镜像地址改成 HTTPS,或者覆盖了 blocker,报错立刻消失。
5.2 三条排查命令定位问题根因
遇到Blocked mirror,如果你的第一反应是“清缓存、删 .m2、重启 IDE”,我建议先停下来。这三件事对这个报错基本没用。真正有用的排查动作按优先级排序如下:
- 执行
mvn help:effective-settings查看合并后的实际配置,确认 blocked 标签和 mirror 是否生效。 - 执行
mvn help:effective-pom看看工程里是否有 HTTP 仓库定义,是否有 profile 覆盖了仓库策略。 - 执行
mvn dependency:resolve -X抓取调试日志,直接搜索Blocked关键字,看完整的匹配链路。
我在第一次排查时,就是因为没看有效设置,只盯着自己写的 settings.xml 看了半天,没意识到默认配置里还藏了一个 blocker。后来打印出 effective-settings,一切真相大白。所以这个顺序一定要记住:先看合并后的配置,再改自己的配置。
5.3 我实际操作中踩过的坑和总结的教训
第一个坑是我曾经以为“只要 mirror 的 URL 是 HTTPS,就不会被 block”。这句话基本正确,但不全对。如果你的 pom.xml 里显式定义了一个 HTTP 仓库,而 mirror 的 mirrorOf 没覆盖到这个仓库 ID,Maven 也会直接阻止对这个仓库的访问。所以除了检查 mirror,还要检查项目 pom.xml 里的<repositories>配置。
第二个坑是覆盖 blocker 时,ID 没写对。我在某台机器上把 ID 写成了maven-default-http-blocker少了中间的横线,结果配置里有两个不同 ID 的 blocker,默认那个依然生效,报错依旧。后来对着官方文档核对 ID 才发现问题。这里千万要一字不差。
第三个坑是关于mirrorOf的排除语法。我一开始想排除私服仓库,写成了*,!private-repo,但 Maven 的 mirrorOf 语法里排除符要放在前面,正确写法是!private-repo,*。搞反了之后,私服请求还是被镜像拦截,导致本机私服完全没法用。这个语法细节不常见,但一旦踩到非常费时间。
第四个坑是 IDE 内置 Maven 和命令行 Maven 的配置环境不同。IDEA 默认使用它自己捆绑的 Maven,如果你只改了系统命令行的 settings.xml,IDEA 里的构建仍然会报错。调整配置时,要记得把 IDEA 的 Maven 设置也指到同一个 settings.xml。这个坑最容易造成“明明改好了,怎么还是报错”的错觉。
最后说点我的个人看法
折腾完这一轮之后,我最大的感受是:Maven 3.8+ 引入的 HTTP 拦截机制,方向是对的,只是对存量用户确实不够友好。大家在升级版本之前,最好先检查一下自己的 settings.xml 里有没有 HTTP 协议的镜像地址,有的话提前换成 HTTPS,省得升级之后一脸懵。
如果你因为公司环境限制,不得不继续使用 HTTP 仓库,那请务必将“覆盖 blocker”的风险告知团队其他成员,并确保内网网络是可信的。安全锁拆掉容易,想装回去就得靠自律了。
最后再分享一个小技巧:以后凡是遇到 Maven 依赖下载相关的诡异报错,先跑一条mvn help:effective-settings -Ddetail=true,把合并后的完整配置拉出来看一眼,很多时候问题根源都藏在你以为“没问题”的默认配置里。这个习惯帮我省下了不少无意义的清缓存时间,希望你也能用上。