Publishing Your Java Library to Maven Central Using GitHub?Actions

Motivation

Publishing your library to Maven Central allows you to easily share it with other users. This article provides an updated guide to publishing on Maven Central, replacing the outdated guide [Outdated]Open-Source Your Java Library by Publishing to Maven Central Using GitHub Actions , because Sonatype, the company behind Maven Central, has transitioned away from publishing via OSSRH.

Prerequisite

  • Github Account
  • GitHub repository for a Java Maven project

Account Setup

  • Create an account on Maven Central .
  • Create a namespace: After registering your account, click on your profile in the top-right corner of the Maven Central portal. In the dropdown menu, select “View Namespaces” to begin setting up your namespace.Create a namespace

Your namespace corresponds to the groupId of a dependency in the pom.xml file. eg.

<dependency>
   <groupId>io.github.innobridge</groupId>
   <artifactId>security</artifactId>
   <version>0.0.1</version>
</dependency>        

If you own a web domain, you can use that as your groupId. If you do not have a domain, you can use your GitHub username. For example, my groupId is io.github.innobridge. Now, click on the "Add Namespace" button, enter your namespace, and click "Submit."

Next, you need to verify your namespace. Click on the “Verify Namespace” button. You will be presented with a verification key. Go to GitHub and create a repository with the same name as the verification key.

Now, click Confirmto complete the verification of your namespace.

Your namespace is now verified.

Repository Setup

In your project’s pom.xml you need to add the following configurations.

<!--Project Coordinates-->
<groupId><!--namespace--></groupId>
<artifactId><!--project name--></artifactId>
<version><!--version number--></version>
<name>${project.groupId}:${project.artifactId}</name>
<description><!--Description--></description>
<url><!--github repo url--></url>

<!--Licence-->
<licenses>
  <license>
    <name><!--name--></name>
    <url><!--license url--></url>
  </license>
</licenses>

<!--Developer Information-->
<developers>
  <developer>
    <id><!--developer id--></id>
    <name><!--developer name--></name>
    <email><!--developer email--></email>
    <organization><!--organization name--></organization>
  </developer>
</developers>

<!--SCM Information-->
<scm>
  <connection>scm:git:git://github.com/<!--Github Profile-->/<!--Repo Name-->.git</connection>
  <developerConnection>scm:git:ssh://github.com:<!--Github Profile-->/<!--Repo Name-->.git</developerConnection>
  <url>https://github.com/<!--Github Profile-->/<!--Repo Name-->/tree/main</url>
</scm>

<!--Maven Plugin for Publishing-->
<build>
 <plugins>
   <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>
    <plugin>
      <groupId>org.sonatype.central</groupId>
      <artifactId>central-publishing-maven-plugin</artifactId>
      <version>0.5.0</version>
      <extensions>true</extensions>
      <configuration>
        <publishingServerId>central</publishingServerId>
        <tokenAuth>true</tokenAuth>
      </configuration>
    </plugin>
    <!-- GPG Signed Components -->
    <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>


<profiles>
  <profile>
    <id>release</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-source-plugin</artifactId>
          <version>3.2.1</version>
          <executions>
            <execution>
              <id>attach-sources</id>
              <goals>
                <goal>jar-no-fork</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>3.3.2</version>
          <executions>
            <execution>
              <id>attach-javadocs</id>
              <goals>
                <goal>jar</goal>
              </goals>
            </execution>
          </executions>
         </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>        

Eg.

    <groupId>io.github.innobridge</groupId>
    <artifactId>security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>${project.groupId}:${project.artifactId}</name>
    <description>Spring Security Library for OAuth2, JWT, and Refresh Token Handling</description>
    <url>https://github.com/InnoBridge/InnoBridgeSecurity</url>

    <licenses>
        <license>
            <name>InnoBridge License Version 1.0</name>
            <url>https://github.com/InnoBridge/License/blob/main/InnoBridgeLicense</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <id>YilengYao</id>
            <name>Yileng Yao</name>
            <email>[email protected]</email>
            <organization>InnoBridgeTechnology</organization>
        </developer>
    </developers>

    <scm>
        <connection>scm:git:git://github.com/InnoBridge/InnoBridgeSecurity.git</connection>
        <developerConnection>scm:git:ssh://github.com:InnoBridge/InnoBridgeSecurity.git</developerConnection>
        <url>https://github.com/InnoBridge/InnoBridgeSecurity/tree/main</url>
    </scm>


<!--Maven Plugin for Publishing-->
<build>
 <plugins>
   <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>
    <plugin>
      <groupId>org.sonatype.central</groupId>
      <artifactId>central-publishing-maven-plugin</artifactId>
      <version>0.5.0</version>
      <extensions>true</extensions>
      <configuration>
        <publishingServerId>central</publishingServerId>
        <tokenAuth>true</tokenAuth>
      </configuration>
    </plugin>
    <!-- GPG Signed Components -->
    <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>

<profiles>
  <profile>
    <id>release</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-source-plugin</artifactId>
          <version>3.2.1</version>
          <executions>
            <execution>
              <id>attach-sources</id>
              <goals>
                <goal>jar-no-fork</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>3.3.2</version>
          <executions>
            <execution>
              <id>attach-javadocs</id>
              <goals>
                <goal>jar</goal>
              </goals>
            </execution>
          </executions>
         </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>        

Add the settings.xml to your github repository.

<?xml version="1.0" encoding="UTF-8"?>
<settings xsi:schemaLocation="https://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"
          xmlns="https://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
    <servers>
        <server>
            <id>central</id>
            <username>${env.MAVEN_USERNAME}</username>
            <password>${env.MAVEN_PASSWORD}</password>
        </server>
    </servers>
    <profiles>
    <profile>
      <id>central</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <gpg.executable>gpg</gpg.executable>
        <gpg.passphrase>${env.MAVEN_GPG_PASSPHRASE}</gpg.passphrase>
      </properties>
    </profile>
  </profiles>
</settings>        

Generating Portal Token for Publishing

  1. Log in to the Central Publisher Portal and navigate to your account at https://central.sonatype.com/account

2. Press the Generate User Token button

3. Confirm the generation (note: this will invalidate an exiting token)

4. Save the generated credentials for use in your publishing setup

That will be your MAVEN_USERNAME and MAVEN_PASSWORD for deploying to Maven Central.

GPG Key Generation

GPG keys are essential for signing your artifact before publishing to Maven Central Repository, ensuring its authenticity and integrity.

Download and install GPG from https://gnupg.org/download/index.html#sec-1-2 ?.

Run the following command to generate your GPG keys, remember to use the email associated with your OSSRH account, and remember your passphrase.

gpg --gen-key        

Then run the following commands to export GPG_PUBLIC_KEY and GPG_SECRET_KEY.

gpg --armor --export YOUR_EMAIL_USED_WHEN_GENERATING_THE_KEY > public-key.gpg
gpg --armor --export-secret-key YOUR_EMAIL_USED_WHEN_GENERATING_THE_KEY > secret-key.gpg        

Your public GPG key will be the file public-key.gpg and secret GPG key will be the file secret-key.gpg

Distributing You Public?key

To distribute your public key to Maven Central so it can verify your files, follow these steps:

  • Listing Your Public Key ID: Run the following command to list your keys:

gpg --list-keys        

The public key ID you need to distribute should be the one associated with your username and email in the UID.

  • To distribute your public key to Maven Central, use the following command to send it to a keyserver:

gpg --keyserver keyserver.ubuntu.com --send-key <public key id>        

Publish your Library with GitHub?Actions

  1. Create the folder?.github/workfows/ in your repository
  2. Add themaven-publish.yml configuration file with the content

name: Deploy to Maven Central

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to publish'
        required: true

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1 - Checkout code
        uses: actions/checkout@v3
        with:
          ref: ${{ github.ref_name }}  # Dynamically use branch name

      - name: Step 2 - Import GPG Key
        run: |
          echo "${{ secrets.GPG_PUBLIC_KEY }}" | gpg --import
          echo "${{ secrets.GPG_SECRET_KEY }}" | gpg --import --no-tty --batch --yes
        env:
          GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
          GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}

      - name: Step 3 - Set up Maven Central Repository
        uses: actions/setup-java@v3
        with:
          java-version: '22'
          distribution: 'temurin'
          server-id: central
          server-username: MAVEN_USERNAME
          server-password: MAVEN_PASSWORD
          gpg-private-key: ${{ secrets.GPG_SECRET_KEY }}
          gpg-passphrase: MAVEN_GPG_PASSPHRASE

      - name: Step 3 - Publish Package to Maven Central
        env:
          MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
          MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
          MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
        run: mvn clean deploy -P release -DskipTests --batch-modeSetting Up Github Secrets        

On your GitHub repository go to Settings then Secrets and variables?, then click Actions

Then click on New repository secret

Then fill the the secret for the secret keys, and click on add secrets

  • MAVEN_USERNAME
  • MAVEN_PASSWORD
  • GPG_SECRET_KEY
  • GPG_PUBLIC_KEY
  • GPG_PASSPHRASE

Triggering a Workflow to Publish your?Package

After merging in your changes, go to the Actions tab, then click on Deploy to Maven Central on the right sidebar. Click on the Run workflow dropdown, select the main branch, enter in your version number and click on Run workflow?.

The github action will deploy your package to Maven Central you can go to https://central.sonatype.com/publishing/deployments to check on your deployment

Your deployment will be in a pending state before your packages has been verified.

Once your packages as been verified and deployment validated you can click on the Publish button to publish your library.

After your package has been published, you can find your library on maven central.

You can now consume this dependency by adding the snippet

<dependency>
  <groupId><!--namespace--></groupId>
  <artifactId><!--project name--></artifactId>
  <version><!--version number--></version>
</dependency>        

to your pom.xml.

Developing Locally

Since Sonatype has migrated to Maven Central, they have discontinued the Staging repository and no longer support SNAPSHOT releases, creating the need for local development and testing before deployment.

To facilitate this, create a docker-compose.yml file that mounts the repository consuming your library and the?.m2 folder:

---
services:

  consumer_application:
    image: openjdk:22-slim
    container_name: consumer-application
    working_dir: /app
    extra_hosts:
      - "localhost:192.168.65.2"
    ports:
      - 8080:8080
      - 5005:5005
    env_file:
      - .env
    volumes:
      - ${PWD}:/app
      - /var/run/docker.sock:/var/run/docker.sock
      - ./local/root:/root
      - ~/.m2:/root/.m2
    tty: true        

Now, run the following command to build and start the container:

docker-compose build && docker-compose uo        

In you library application append -SNAPSHOTto your version and run

./mvnw clean install        

Next, in the pom.xml of your consumer application, add the dependency for your library:

<dependency>
  <groupId><!--namespace--></groupId>
  <artifactId><!--project name--></artifactId>
  <version><!--version number--></version>
</dependency>        

Now, SSH into the Docker container of your consumer application:

docker exec -it consumer-application sh        

Finally, run the following command to start your consumer application:

./mvnw spring-boot:run        


要查看或添加评论,请登录