I have this library that I work on from time to time, that I published on Maven Central. A few days ago I released a new version, and since I always forget what the exact mvn command is, what my GPG passphrase is, etc. I thought it would be a good opportunity to automate the release process using GitHub Actions.

I came across several outdated tutorials that create settings.xml and import GPG keys manually, but as of Feb. 2021 most of this is not needed anymore, thanks to recent changes in the setup-java action.

Prerequisites

From now on, I will assume that you already know how to deploy on Maven Central via Sonatype OSSRH. This means you created an account on Sonatype’s Jira, your local settings.xml is already configured and your pom.xml has a <plugins> section that looks more or less like this:

<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.sonatype.plugins</groupId>
            <artifactId>nexus-staging-maven-plugin</artifactId>
            <version>1.6.8</version>
            <extensions>true</extensions>
            <configuration>
                <serverId>ossrh</serverId>
                <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                <autoReleaseAfterClose>true</autoReleaseAfterClose>
            </configuration>
        </plugin>
        ...
    </plugins>
</build>

<profiles>
    <profile>
        <id>release</id>
        <build>
            <plugins>
                ...
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                    <version>1.6</version>
                    <executions>
                        <execution>
                            <id>sign-artifacts</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>sign</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                ...
            </plugins>
        </build>
    </profile>
</profiles>

Entering GitHub Actions

Java Actions workflows often use a setup-java action which… well… sets up Java in the build runner:

...
    - name: Set up JDK 11
      uses: actions/setup-java@v1
      with:
        java-version: 11

I thought this action was only used to download a JDK, but it turns out it can do more than that: it also knows how to set up the runner to publish artifacts on Maven Central (or any <distributionManagement> configured in your pom.xml, for that matter):

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8

    - name: Build with Maven
      run: mvn -B package --file pom.xml

    - name: Set up Apache Maven Central
      uses: actions/setup-java@v1
      with: # running setup-java again overwrites the settings.xml
        java-version: 1.8
        server-id: maven # Value of the distributionManagement/repository/id field of the pom.xml
        server-username: MAVEN_USERNAME # env variable for username in deploy
        server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
        gpg-private-key: $ # Value of the GPG private key to import
        gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase

    - name: Publish to Apache Maven Central
      run: mvn deploy
      env:
        MAVEN_USERNAME: maven_username123
        MAVEN_CENTRAL_TOKEN: $
        MAVEN_GPG_PASSPHRASE: $

As explained in the readme, the second invocation of actions/setup-java@v1 will overwrite the runner’s settings.xml with your Sonatype credentials and GPG passphrase, using environment variables:

<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">
  <servers>
    <server>
      <id>maven</id>
      <username>${env.MAVEN_USERNAME}</username>
      <password>${env.MAVEN_CENTRAL_TOKEN}</password>
    </server>
    <server>
      <id>gpg.passphrase</id>
      <passphrase>${env.MAVEN_GPG_PASSPHRASE}</passphrase>
    </server>
  </servers>
</settings>

The private GPG key stored in the MAVEN_GPG_PRIVATE_KEY secret will also be imported in a GPG keychain, allowing maven-gpg-plugin to sign your artifacts correctly.

Adding the missing pieces

In order to make the workflow run smoothly, I needed two additional pieces of information that are not (yet) mentionned in the readme:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.6</version>
    <configuration>
        <!-- Prevent gpg from using pinentry programs -->
        <gpgArguments>
            <arg>--pinentry-mode</arg>
            <arg>loopback</arg>
        </gpgArguments>
    </configuration>
    ...
</plugin>

Final note

Setting up this workflow was not that difficult, except for the two missing pieces that I fortunately fixed very easily, thanks to issues and comments from other people who had the same problem before me. 10/10, would recommend 😜.

My final release workflow is available on GitHub.