Maven Central 发布

返回构建工具

将开源 Java 库发布到 Maven Central,使任何项目都能通过 groupId/artifactId/version 依赖。


发布渠道概览

历史(~2024 年 2 月前):
  开发者 → OSSRH(Sonatype Nexus)→ Maven Central
  需要申请 Sonatype Jira 账号,流程繁琐

现在(Central Portal,2024 年 2 月起):
  开发者 → Central Portal(central.sonatype.com)→ Maven Central
  直接在 Web 界面注册,更简洁
  旧 OSSRH 账号仍可用,但新项目推荐用 Central Portal

两种渠道最终都发布到同一个 Maven Central,使用者不感知区别。


前置条件

1. Coordinates(坐标)命名规范

<groupId>io.github.your-username</groupId>   <!-- 必须拥有该命名空间 -->
<artifactId>your-library</artifactId>
<version>1.0.0</version>

groupId 命名空间:必须是你控制的域名反转,常见方式:

  • 自有域名:com.example(需验证 DNS TXT 记录)
  • GitHub:io.github.your-username(通过 GitHub 账号验证)
  • GitLab:io.gitlab.your-username

2. POM 必填元素

Maven Central 要求 POM 包含以下元素(否则发布被拒绝):

<project>
    <groupId>io.github.your-username</groupId>
    <artifactId>your-library</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
 
    <!-- 必填:项目名称、描述、URL -->
    <name>Your Library</name>
    <description>A brief description of what this library does.</description>
    <url>https://github.com/your-username/your-library</url>
 
    <!-- 必填:开源许可证 -->
    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0</url>
        </license>
    </licenses>
 
    <!-- 必填:开发者信息 -->
    <developers>
        <developer>
            <id>your-username</id>
            <name>Your Name</name>
            <email>you@example.com</email>
        </developer>
    </developers>
 
    <!-- 必填:SCM(源码管理)信息 -->
    <scm>
        <connection>scm:git:git://github.com/your-username/your-library.git</connection>
        <developerConnection>scm:git:ssh://github.com/your-username/your-library.git</developerConnection>
        <url>https://github.com/your-username/your-library/tree/main</url>
    </scm>
</project>

3. 必须上传的文件

每个构件(jar/pom/sources/javadoc)都需要:

  • .jar 本体
  • -sources.jar(源码包)
  • -javadoc.jar(文档包)
  • .pom 文件
  • 上述每个文件的 .asc GPG 签名文件
  • 上述每个文件的 .md5 / .sha1 校验和(Maven 插件自动生成)

GPG 签名配置

所有上传到 Maven Central 的构件必须有 GPG 签名。

→ 详细 GPG 操作见 OpenPGP

# 确认已有 GPG 密钥
gpg --list-keys
 
# 获取密钥 ID(用于 Maven/Gradle 配置)
gpg --list-keys --keyid-format SHORT
# 输出示例:pub   ed25519/ABCD1234 2024-01-01
 
# 将公钥上传到密钥服务器(Maven Central 校验签名时需要)
gpg --keyserver hkps://keyserver.ubuntu.com --send-keys ABCD1234
gpg --keyserver hkps://keys.openpgp.org --send-keys ABCD1234

注册 Central Portal

  1. 访问 central.sonatype.com → Sign In(用 GitHub 账号登录)
  2. View Namespaces → Add Namespace
  3. 输入 namespace(如 io.github.your-username
  4. 按提示完成验证:
    • GitHub namespace:在 GitHub 创建指定名称的 public 仓库
    • 自有域名:添加 DNS TXT 记录
  5. 等待验证通过(通常几分钟~1 小时)

Maven 配置(推荐方式)

pom.xml:添加必要插件

<build>
    <plugins>
        <!-- 生成 sources jar -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.3.0</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <goals><goal>jar-no-fork</goal></goals>
                </execution>
            </executions>
        </plugin>
 
        <!-- 生成 javadoc jar -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>3.6.3</version>
            <executions>
                <execution>
                    <id>attach-javadocs</id>
                    <goals><goal>jar</goal></goals>
                </execution>
            </executions>
        </plugin>
 
        <!-- GPG 签名所有构件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-gpg-plugin</artifactId>
            <version>3.2.4</version>
            <executions>
                <execution>
                    <id>sign-artifacts</id>
                    <phase>verify</phase>
                    <goals><goal>sign</goal></goals>
                    <configuration>
                        <!-- CI 环境下用 loopback 避免交互弹窗 -->
                        <gpgArguments>
                            <arg>--pinentry-mode</arg>
                            <arg>loopback</arg>
                        </gpgArguments>
                    </configuration>
                </execution>
            </executions>
        </plugin>
 
        <!-- Central Portal 发布插件(新版推荐)-->
        <plugin>
            <groupId>org.sonatype.central</groupId>
            <artifactId>central-publishing-maven-plugin</artifactId>
            <version>0.5.0</version>
            <extensions>true</extensions>
            <configuration>
                <publishingServerId>central</publishingServerId>
                <!-- true = 自动发布;false = 上传后手动在 Portal 点击发布 -->
                <autoPublish>false</autoPublish>
                <waitUntil>validated</waitUntil>
            </configuration>
        </plugin>
    </plugins>
</build>

settings.xml:配置 Central Portal 凭据

<!-- ~/.m2/settings.xml -->
<servers>
    <server>
        <id>central</id>
        <username><!-- Central Portal 生成的 Token username --></username>
        <password><!-- Central Portal 生成的 Token password --></password>
    </server>
</servers>

生成 Token:Central Portal → Account → Generate User Token

执行发布

# 完整构建并发布到 Central Portal 暂存区
mvn clean deploy -P release
 
# 发布后在 Central Portal → Deployments 查看状态并手动 Publish
# 或配置 autoPublish=true 自动发布

Gradle 配置

build.gradle.kts

plugins {
    `java-library`
    `maven-publish`
    signing
    id("com.gradleup.nmcp") version "0.0.9"   // Central Portal 发布插件
}
 
java {
    withSourcesJar()
    withJavadocJar()
}
 
publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            from(components["java"])
 
            pom {
                name.set("Your Library")
                description.set("A brief description.")
                url.set("https://github.com/your-username/your-library")
 
                licenses {
                    license {
                        name.set("Apache License, Version 2.0")
                        url.set("https://www.apache.org/licenses/LICENSE-2.0")
                    }
                }
                developers {
                    developer {
                        id.set("your-username")
                        name.set("Your Name")
                        email.set("you@example.com")
                    }
                }
                scm {
                    connection.set("scm:git:git://github.com/your-username/your-library.git")
                    developerConnection.set("scm:git:ssh://github.com/your-username/your-library.git")
                    url.set("https://github.com/your-username/your-library")
                }
            }
        }
    }
}
 
signing {
    // CI 环境从环境变量读取密钥
    val signingKey: String? by project
    val signingPassword: String? by project
    if (signingKey != null) {
        useInMemoryPgpKeys(signingKey, signingPassword)
    } else {
        useGpgCmd()   // 本地开发使用系统 GPG
    }
    sign(publishing.publications["mavenJava"])
}
 
nmcp {
    // gradle.properties 或环境变量中读取
    username.set(providers.gradleProperty("centralUsername"))
    password.set(providers.gradleProperty("centralPassword"))
    publicationType.set("USER_MANAGED")   // 手动发布
    // 或 "AUTOMATIC" 自动发布
}

gradle.properties(本地,不提交到 git)

# ~/.gradle/gradle.properties
centralUsername=<Central Portal Token username>
centralPassword=<Central Portal Token password>

执行发布

./gradlew publishAllPublicationsToNmcpRepository
# 然后去 Central Portal 手动 Publish

GitHub Actions 自动发布

tag 推送时自动构建并发布到 Maven Central:

# .github/workflows/release.yml
name: Release to Maven Central
 
on:
  push:
    tags:
      - 'v*'
 
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          server-id: central
          server-username: MAVEN_USERNAME
          server-password: MAVEN_PASSWORD
          gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
          gpg-passphrase: MAVEN_GPG_PASSPHRASE
 
      - name: Deploy to Maven Central
        run: mvn --no-transfer-progress -B deploy -P release
        env:
          MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
          MAVEN_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
          MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

GitHub Secrets 配置:

Secret 名称内容
GPG_PRIVATE_KEYgpg --armor --export-secret-keys KEY_ID 导出的私钥
GPG_PASSPHRASEGPG 密钥密码
CENTRAL_USERNAMECentral Portal Token username
CENTRAL_PASSWORDCentral Portal Token password
# 导出 GPG 私钥用于 GitHub Secrets
gpg --armor --export-secret-keys your@email.com | cat

版本规范

版本类型格式示例说明
RELEASE1.0.01.2.3正式发布版,发布后不可修改
SNAPSHOT1.0.1-SNAPSHOT开发版,可重复发布覆盖,不进入 Maven Central

SNAPSHOT 版本只能发布到私有仓库或 OSSRH SNAPSHOT 仓库,不能发布到 Maven Central 正式仓库。


发布检查清单

□ POM 包含必填字段(name/description/url/licenses/developers/scm)
□ 版本号为 RELEASE(不含 -SNAPSHOT)
□ GPG 密钥已上传到公共密钥服务器
□ 生成 sources jar(-sources.jar)
□ 生成 javadoc jar(-javadoc.jar)
□ 所有文件有 GPG 签名(.asc)
□ namespace 在 Central Portal 已验证
□ Central Portal Token 凭据已配置
□ 本地测试:mvn verify 通过

常见错误

错误原因解决
Missing required POM elementsPOM 缺少 name/description/url/licenses/developers/scm补充必填字段
GPG key not found on keyserver公钥未上传到密钥服务器gpg --send-keys KEY_ID
Signature invalid签名文件与构件不匹配重新构建并签名
Namespace not verifiedgroupId 对应命名空间未在 Portal 验证完成 namespace 验证流程
Version already existsRELEASE 版本已发布,不可覆盖升版本号重新发布
javadoc errorsJavadoc 生成失败修复 Javadoc 格式,或临时 -Djavadoc.failOnError=false

OSSRH 旧流程(已注册账号的项目)

如果是 2024 年前注册的旧 OSSRH 账号,发布到 s01.oss.sonatype.org

<!-- pom.xml distributionManagement -->
<distributionManagement>
    <snapshotRepository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
    </snapshotRepository>
    <repository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
</distributionManagement>
 
<!-- 使用 nexus-staging-maven-plugin 代替 central-publishing-maven-plugin -->
<plugin>
    <groupId>org.sonatype.plugins</groupId>
    <artifactId>nexus-staging-maven-plugin</artifactId>
    <version>1.6.13</version>
    <extensions>true</extensions>
    <configuration>
        <serverId>ossrh</serverId>
        <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
        <autoReleaseAfterClose>true</autoReleaseAfterClose>
    </configuration>
</plugin>

相关