Skip to content

Publishing to Maven Central with GitHub Actions

Publish a Java library to Maven Central via GitHub Actions.

Note

This guide assumes you have a Sonatype account and a GPG key for signing JARs.

Publishing to Maven Central makes your Java library easily accessible to developers via a trusted, widely-used repository. This guide walks you through automating the release process using GitHub Actions.

Setting Up a GitHub Environment

Create a GitHub Environment

  1. Navigate to your GitHub repository's Settings page.
  2. Under Code and automation, navigate to the Environments tab.
  3. Select New environment, and name it "central".

Add Environment Secrets

Create the following environment secrets.

GPG_KEY

Your full GPG Private Key in ASCII armor format.(1)(2)

  1. The key block should look something like this:
    -----BEGIN PGP PRIVATE KEY BLOCK-----
    
    lQdGBGfwSTMBEAC3A2g0DOL38UTj6ph9m9cr9059UgIx9PsO1Xxwp0GhC5WqESU2
    zgHLRD9zdRD80LMbZgK7j7abkIs+NJeb0BwPR6noHWNCsRqpzL6RYW4u4Z/a1iqn
    6Tah5SdnKcyCSviHNSZAKyLWcliOReiaTln2DmDqD0Hz1blZ+BX1u/V0aTrBE7aY
    KFuGcNzL3EmN+ef4Q0t1dkw8sxIQSQ5ldKmqBl0TSk7ruvRoxsghiiKsm7Oguizi
    /nXzLezXtcSzsHLsCdpX5WIrWxKzJRhqzOXyRRGV88/fp96Yh2b4HAM71a13GeUf
    3xvh08lWwRJQ8fUwx9JN5sLKmV4CCElWtB1SU8LJKl0S4HGmSfU0IYmZfI+pNB20
    NO1pA9izQte4hamhl+udw2tQzeWrw52CCsLjShVWUoXDoQN/CLt1fM9U8WbcVqWx
    A81HlBeVrPIUB2gnfpzri7Y78lsplG9yZ33d9+QOyme9v/wsXbLw0uocoV07XLJF
    6nyI3YO/Mx1fE/WbTg+eyx7lFuqTwN+PWM627zNndgcG
    =z5qh
    -----END PGP PRIVATE KEY BLOCK-----
    
  2. Learn how to create and upload a GPG key.
Tip

Print your key in ASCII armor format with this command, replacing ABC123DEF4567890 with your actual key ID.

gpg --armor --export ABC123DEF4567890

GPG_PASSPHRASE

This is the passphrase you created with your GPG Key.(1)

  1. You were likely prompted for this passphrase when exporting your key for GPG_KEY.
OSSRH_USERNAME & OSSRH_PASSWORD
These can both be obtained from your Sonatype account details by clicking Generate User Token.

Workflow Creation

Create a workflow config in .github/workflows.

maven-publish.yml
name: Auto Publish
on:
  release:
    types: [ created ]
jobs:
  publish:
    runs-on: ubuntu-latest
    environment: central
    steps:
      - uses: actions/checkout@v4
      - name: Set up Maven Central Repository
        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_KEY }}
          gpg-passphrase: MAVEN_GPG_PASSPHRASE
      - name: Set version
        # Strip the leading 'v' from tag (e.g., v1.2.3 → 1.2.3)
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          mvn versions:set -DnewVersion=$VERSION
      - name: Publish package
        run: mvn -P release --batch-mode deploy -DskipTests
        env:
          MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
          MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
          MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
Automatic Publish Workflow Breakdown
on:
  release:
  types: [ created ]
  • Triggers the workflow automatically whenever a new GitHub Release is created.
  • Ideal for projects with Git-based versioning.
- name: Set up Maven Central Repository
  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_KEY }}
    gpg-passphrase: MAVEN_GPG_PASSPHRASE
  • Sets up Java 21 with Temurin and configures Maven for signed deployments.
  • The server-id must match the <id> used in your pom.xml's <distributionManagement> section.
  • Also imports your GPG key so artifacts can be signed as required by Maven Central.
- name: Set version
  run: |
  VERSION=${GITHUB_REF_NAME#v}
  mvn versions:set -DnewVersion=$VERSION
  • Extracts the version number from the GitHub release tag (e.g., v1.2.01.2.0).
- name: Publish package
  run: mvn -P release --batch-mode deploy -DskipTests
  env:
  MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
  MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
  MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
  • Builds, signs, and deploys the project to Maven Central using the release profile.
  • Tests are skipped to reduce CI time. Remove -DskipTests if untested code is frequently pushed to your repo.
manual-maven-publish.yml
name: Manual Publish
on:
  workflow_dispatch:
    inputs:
      version:
        type: string
        description: Version number for this release. Must be SemVer compliant.
        required: true
      dry_run:
        type: boolean
        description: 'Run build without deploying'
        required: false
        default: false
jobs:
    publish:
        runs-on: ubuntu-latest
        environment: central
        steps:
            - uses: actions/checkout@v4
            - name: Set up Maven Central Repository
              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_KEY }}
                  gpg-passphrase: MAVEN_GPG_PASSPHRASE
            - name: Set version
              run: mvn versions:set -DnewVersion=${{ github.event.inputs.version }}
            - name: Publish package
              run: |
                if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
                  echo "Running dry run (no deploy)..."
                  mvn clean verify -P release -Dgpg.skip
                else
                  echo "Deploying to Maven Central..."
                  mvn -P release --batch-mode deploy -DskipTests
                fi
              env:
                MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
                MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
                MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
Manual Publish Workflow Breakdown
on:
  workflow_dispatch:
    inputs:
      version:
        type: string
        description: Version number for this release. Must be SemVer compliant.
        required: true
      dry_run:
        type: boolean
        description: 'Run build without deploying'
        required: false
        default: false
  • Defines a manual trigger with inputs for version and dry run mode.
- name: Set up Maven Central Repository
  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_KEY }}
    gpg-passphrase: MAVEN_GPG_PASSPHRASE
  • Sets up Java 21 with Temurin and configures Maven for signed deployments.
  • The server-id must match the ID in your pom.xml <distributionManagement>.
  • Also imports your GPG key so artifacts can be signed as required by Maven Central.
- name: Set version
  run: mvn versions:set -DnewVersion=${{ github.event.inputs.version }}
  • Applies the manually provided version input to the Maven project.
- name: Publish package
  run: |
    if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
      echo "Running dry run (no deploy)..."
      mvn clean verify -P release -Dgpg.skip
    else
      echo "Deploying to Maven Central..."
      mvn -P release --batch-mode deploy -DskipTests
    fi
  env:
    MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
    MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
    MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
  • If dry_run is true, it builds the project and generates signed artifacts, but does not deploy.
  • If dry_run is false, it performs a full release to Maven Central.

Maven Configuration

Modify your pom.xml to include distribution management & a profile for signing & publishing.

Ensure Required POM Metadata

Maven Central requires your pom.xml to include specific metadata for your library to be accepted:

  • <name>: The name of your library.
  • <description>: A short description of what it does.
  • <url>: Project homepage or GitHub repo.
  • <licenses>: Must include valid SPDX-compliant license info.
  • <developers>: At least one developer entry.
  • <scm>: Source control metadata including connection and developerConnection.

These values are automatically validated during deployment. Missing or invalid fields will cause Sonatype to reject the upload.

🔗 View full requirements

Here's an example pom.xml from Wraith, my Java event library.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--https://maven.apache.org/guides/mini/guide-naming-conventions.html-->
    <groupId>dev.7ori</groupId>
    <artifactId>wraith</artifactId>
    <version>4.1.0</version>

    <packaging>jar</packaging>

    <name>Wraith</name>
    <description>Capable, versatile, and easy to use Java event library.
    </description>
    <url>https://github.com/7orivorian/Wraith</url>

    <licenses>
        <license>
            <name>MIT</name>
            <url>https://www.mit.edu/~amini/LICENSE.md</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <id>7orivorian</id>
            <name>Tori</name>
            <url>https://7ori.dev</url>
        </developer>
    </developers>

    <scm>
        <url>https://github.com/7orivorian/Wraith</url>
        <connection>scm:git:git://github.com/7orivorian/Wraith.git</connection>
        <developerConnection>scm:git:ssh://git@github.com/7orivorian/Wraith.git</developerConnection>
    </scm>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains</groupId>
            <artifactId>annotations</artifactId>
            <version>26.0.2</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Java Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <!-- Source and Javadoc JARs -->
            <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</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <additionalOptions>
                        <additionalOption>-Xdoclint:none</additionalOption>
                    </additionalOptions>
                </configuration>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <distributionManagement>
        <repository>
            <id>central</id>
            <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
        <snapshotRepository>
            <id>central</id>
            <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
        </snapshotRepository>
    </distributionManagement>

    <profiles>
        <profile>
            <id>release</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.sonatype.central</groupId>
                        <artifactId>central-publishing-maven-plugin</artifactId>
                        <version>0.7.0</version>
                        <extensions>true</extensions>
                        <configuration>
                            <deploymentName>Wraith</deploymentName>
                            <publishingServerId>central</publishingServerId>
                            <autoPublish>true</autoPublish>
                            <checksums>all</checksums>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>3.1.0</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <gpgArguments>
                                <arg>--pinentry-mode</arg>
                                <arg>loopback</arg>
                            </gpgArguments>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Here are the most important snippets. Remember to replace any highlighted sections as they contain project or user specific data.

<distributionManagement>
    <repository>
        <id>central</id>
        <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
    <snapshotRepository>
        <id>central</id>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
    </snapshotRepository>
</distributionManagement>
<profiles>
    <profile>
        <id>release</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.sonatype.central</groupId>
                    <artifactId>central-publishing-maven-plugin</artifactId>
                    <version>0.7.0</version>
                    <extensions>true</extensions>
                    <configuration>
                        <deploymentName>Wraith</deploymentName>
                        <publishingServerId>central</publishingServerId>
                        <autoPublish>true</autoPublish>
                        <checksums>all</checksums>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                    <version>3.1.0</version>
                    <executions>
                        <execution>
                            <id>sign-artifacts</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>sign</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <gpgArguments>
                            <arg>--pinentry-mode</arg>
                            <arg>loopback</arg>
                        </gpgArguments>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
<scm>
    <url>https://github.com/7orivorian/Wraith</url>
    <connection>scm:git:git://github.com/7orivorian/Wraith.git</connection>
    <developerConnection>scm:git:ssh://git@github.com/7orivorian/Wraith.git</developerConnection>
</scm>