Publishing to Maven Central

Maven Central is the defacto artifact repository for JVM based projects. Anyone can publish artifacts to it as long as they follow the rules. Follow this guide to register an account. You also must comply with all requirements otherwise deployment will fail. Fortunately JReleaser can verify many of those requirements before any artifacts are deployed.

Before continuing make sure that all artifacts to be deployed have been staged. Missing this step will make the deployment phase fail!

Maven Central requires artifacts to be signed with PGP. The Nexus2 deployer automatically enables applyMavenCentralRules when the publication url contains oss.sonatype.org. This setting performs the following tasks:

  • verify POMs comply with publication rules (using PomChecker).

  • assert that matching -sources.jar and -javadoc.jar artifacts have been staged (when applicable).

  • calculate md5, sha1, sha256, and sha512 checksums for all staged artifacts.

  • sign all staged artifacts.

You have the option to close and release the staged repository automatically right after deployment, or keep the staged repository open and perform close and release operations using the UI. You must login into Sonatype OSSRH using your Sonatype account to do so.

We recommend setting releaseRepository to false for the first time. This lets you review staged artifacts in the Sonatype UI. You’ll have to perform a manual release on the UI. Once you’re happy with the settings and the release is successful you may switch this property to true.
As described here, Sonatype projects created before February 2021 may need to use "https://oss.sonatype.org/service/local" instead of "https://s01.oss.sonatype.org/service/local". Using incorrect url may cause a 'Could not find a staging profile matching …​' error.
  • YAML

  • TOML

  • JSON

  • Maven

  • Gradle

signing:
  active: ALWAYS
  armored: true

deploy:
  maven:
    nexus2:
      maven-central:
        active: ALWAYS
        url: https://s01.oss.sonatype.org/service/local
        snapshotUrl: https://s01.oss.sonatype.org/content/repositories/snapshots/
        closeRepository: true
        releaseRepository: true
        stagingRepositories:
          - target/staging-deploy
[signing]
  active = "ALWAYS"
  armored = true

[deploy.maven.nexus2.maven-central]
  active = "ALWAYS"
  url = "https://s01.oss.sonatype.org/service/local"
  snapshotUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
  closeRepository = true
  releaseRepository = true
  stagingRepositories = ["target/staging-deploy"]
{
  "jreleaser": {
    "signing": {
      "active": "ALWAYS",
      "armored": true
    },
    "deploy": {
      "maven": {
        "nexus2": {
          "maven-central": {
            "active": "ALWAYS",
            "url": "https://s01.oss.sonatype.org/service/local",
            "snapshotUrl": "https://s01.oss.sonatype.org/content/repositories/snapshots/",
            "closeRepository": true,
            "releaseRepository": true,
            "stagingRepositories": [
              "target/staging-deploy"
            ]
          }
        }
      }
    }
  }
}
<jreleaser>
  <signing>
    <active>ALWAYS</active>
    <armored>true</armored>
  </signing>
  <deploy>
    <maven>
      <nexus2>
        <maven-central>
          <active>ALWAYS</active>
          <url>https://s01.oss.sonatype.org/service/local</url>
          <snapshotUrl>https://s01.oss.sonatype.org/content/repositories/snapshots/</snapshotUrl>
          <closeRepository>true</closeRepository>
          <releaseRepository>true</releaseRepository>
          <stagingRepositories>target/staging-deploy</stagingRepositories>
        </maven-central>
      </nexus2>
    </maven>
  </deploy>
</jreleaser>
jreleaser {
  signing {
    active = 'ALWAYS'
    armored = true
  }
  deploy {
    maven {
      nexus2 {
        maven-central {
          active = 'ALWAYS'
          url = 'https://s01.oss.sonatype.org/service/local'
          snapshotUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
          closeRepository = true
          releaseRepository = true
          stagingRepository('target/staging-deploy')
        }
      }
    }
  }
}

The following secrets must be configured either using environment variables or the secrets configuration file:

  • JRELEASER_GPG_PUBLIC_KEY, unless signing.verify is set to false.

  • JRELEASER_GPG_SECRET_KEY

  • JRELEASER_GPG_PASSPHRASE

  • JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME or JRELEASER_NEXUS2_USERNAME

  • JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD or JRELEASER_NEXUS2_PASSWORD

Maven

The following pom.xml file shows the minimum required configuration

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.acme</groupId>
    <artifactId>app</artifactId>
    <version>1.0.0</version>

    <name>app</name>
    <description>Sample application</description>
    <url>https://github.com/aalmiray/app</url>
    <inceptionYear>2021</inceptionYear>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.release>11</maven.compiler.release>
    </properties>

    <licenses>
        <license>
            <name>Apache-2.0</name>
            <url>https://spdx.org/licenses/Apache-2.0.html</url>
            <distribution>repo</distribution>
        </license>
    </licenses>

    <developers>
        <developer>
            <id>aalmiray</id>
            <name>Andres Almiray</name>
        </developer>
    </developers>

    <scm>
        <connection>scm:git:https://github.com/aalmiray/app.git</connection>
        <developerConnection>scm:git:https://github.com/aalmiray/app.git</developerConnection>
        <url>https://github.com/aalmiray/app.git</url>
        <tag>HEAD</tag>
    </scm>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.10.1</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                    <version>3.2.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <version>3.2.1</version>
                </plugin>
                <plugin>
                    <groupId>org.jreleaser</groupId>
                    <artifactId>jreleaser-maven-plugin</artifactId>
                    <version>1.8.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.jreleaser</groupId>
                <artifactId>jreleaser-maven-plugin</artifactId>
                <configuration>
                    <jreleaser>
                        <signing>
                            <active>ALWAYS</active>
                            <armored>true</armored>
                        </signing>
                      <deploy>
                          <maven>
                              <nexus2>
                                  <maven-central>
                                      <active>ALWAYS</active>
                                      <url>https://s01.oss.sonatype.org/service/local</url>
                                      <snapshotUrl>https://s01.oss.sonatype.org/content/repositories/snapshots/</snapshotUrl>
                                      <closeRepository>true</closeRepository>
                                      <releaseRepository>true</releaseRepository>
                                      <stagingRepositories>target/staging-deploy</stagingRepositories>
                                  </maven-central>
                              </nexus2>
                          </maven>
                      </deploy>
                    </jreleaser>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <profiles>
        <profile>
            <id>publication</id>
            <properties>
                <altDeploymentRepository>local::file:./target/staging-deploy</altDeploymentRepository>
            </properties>
            <build>
                <defaultGoal>deploy</defaultGoal>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>attach-javadocs</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                                <configuration>
                                    <attach>true</attach>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>attach-sources</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                                <configuration>
                                    <attach>true</attach>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Store secrets in ~/.jreleaser/config.toml. The TOML format is chosen as it allows multiline strings which are required for defining both public and secret keys. You may opt instead to use the YAML format in which case be aware of indentation or use environment variables.

~/.jreleaser/config.toml
JRELEASER_NEXUS2_USERNAME = "<your-sonatype-account-username>"
JRELEASER_NEXUS2_PASSWORD = "<your-sonatype-account-password>"
JRELEASER_GPG_PASSPHRASE = "<your-pgp-passphrase>"

JRELEASER_GPG_PUBLIC_KEY="""-----BEGIN PGP PUBLIC KEY BLOCK-----

<contents-of-your-public-key>

-----END PGP PUBLIC KEY BLOCK-----"""

JRELEASER_GPG_SECRET_KEY="""-----BEGIN PGP PRIVATE KEY BLOCK-----

<contents-of-your-private-key>

-----END PGP PRIVATE KEY BLOCK-----"""

You may export public and private keys with the following commands:

$ gpg --output public.pgp --armor --export username@email
$ gpg --output private.pgp --armor --export-secret-key username@email

If you do not wish to configure public and private keys in the secrets configuration file you may directly refer to the exported key files, in which case signing configuration should be updated to the following:

<jreleaser>
    <signing>
      <active>ALWAYS</active>
      <armored>true</armored>
      <mode>FILE</mode>
      <publicKey>path/to/public.pgp</publicKey>
      <secretKey>path/to/private.pgp</secretKey>
    </signing>
</jreleaser>
DO NOT commit public and private key files to source control!

Once all configuration is in place you may execute the following commands:

1) Verify release & deploy configuration

$ mvn jreleaser:config

2) Stage all artifacts to a local directory

$ mvn -Ppublication

3) Deploy and release

$ mvn jreleaser:full-release

Gradle

The following build.gradle file shows the minimum required configuration

build.gradle
plugins {
    id 'java-library'
    id 'maven-publish'
    id 'org.jreleaser' version '1.8.0'
}

java {
    withJavadocJar()
    withSourcesJar()
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'com.acme'
            artifactId = 'app'

            from components.java

            pom {
                name = 'app'
                description = 'Sample application'
                url = 'https://github.com/aalmiray/app'
                inceptionYear = '2021'
                licenses {
                    license {
                        name = 'Apache-2.0'
                        url = 'https://spdx.org/licenses/Apache-2.0.html'
                    }
                }
                developers {
                    developer {
                        id = 'aalmiray'
                        name = 'Andres Almiray'
                    }
                }
                scm {
                    connection = 'scm:git:https://github.com/aalmiray/app.git'
                    developerConnection = 'scm:git:ssh://github.com/aalmiray/app.git'
                    url = 'http://github.com/aalmiray/app'
                }
            }
        }
    }

    repositories {
        maven {
            url = layout.buildDirectory.dir('staging-deploy')
        }
    }
}

jreleaser {
    signing {
        active = 'ALWAYS'
        armored = true
    }
    deploy {
        maven {
            nexus2 {
                'maven-central' {
                    active = 'ALWAYS'
                    url = 'https://s01.oss.sonatype.org/service/local'
                    snapshotUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
                    closeRepository = true
                    releaseRepository = true
                    stagingRepository('build/staging-deploy')
                }
            }
        }
    }
}

Store secrets in ~/.jreleaser/config.toml. The TOML format is chosen as it allows multiline strings which are required for defining both public and secret keys. You may opt instead to use the YAML format in which case be aware of indentation or use environment variables.

~/.jreleaser/config.toml
JRELEASER_NEXUS2_USERNAME = "<your-sonatype-account-username>"
JRELEASER_NEXUS2_PASSWORD = "<your-sonatype-account-password>"
JRELEASER_GPG_PASSPHRASE = "<your-pgp-passphrase>"

JRELEASER_GPG_PUBLIC_KEY="""-----BEGIN PGP PUBLIC KEY BLOCK-----

<contents-of-your-public-key>

-----END PGP PUBLIC KEY BLOCK-----"""

JRELEASER_GPG_SECRET_KEY="""-----BEGIN PGP PRIVATE KEY BLOCK-----

<contents-of-your-private-key>

-----END PGP PRIVATE KEY BLOCK-----"""

You may export public and private keys with the following commands:

$ gpg --output public.pgp --armor --export username@email
$ gpg --output private.pgp --armor --export-secret-key username@email

If you do not wish to configure public and private keys in the secrets configuration file you may directly refer to the exported key files, in which case signing configuration should be updated to the following:

build.gradle
jreleaser {
    signing {
        active = 'ALWAYS'
        armored = true
        mode = 'FILE'
        publicKey = 'path/to/public.pgp'
        secreteKey = 'path/to/private.pgp'
    }
}
DO NOT commit public and private key files to source control!

Once all configuration is in place you may execute the following commands:

1) Verify release & deploy configuration

$ ./gradlew jreleaserConfig

2) Stage all artifacts to a local directory

$ ./gradlew publish

3) Deploy and release

$ ./gradlew jreleaserFullRelease