Англоязычная версия статьи на Medium
Начиная с 12 марта 2024 года регистрация на OSSRH портале теперь недоступна. Большинство существующих туториалов в интернете описывают как раз опыт публикации через OSSRH на Maven Central из-за чего после марта 2024 года эти инструкции стали не актуальны для публикации проектов новых авторов.
Disclaimer: я не смогу провести вас по этому процессу шаг за шагом, потому что разные проекты работают по разному. Ниже — не пошаговая инструкция, а руководство к действию. Вдумчиво выполняйте этапы публикации и не забывайте про секцию Troubleshoting в конце статьи
Процесс публикации можно разбить на следующие шаги:
-
Регистрация на Central Portal и верификация namespace
-
Создания GPG ключа для подписи артефактов
-
Локальная публикация проекта для теста
-
Подключение JReleaser к проекту и публикация локально
-
Настройка Github Actions для автоматической публикации
Регистрация на Central Portal и верификация namespace
Зарегистрироваться на портале можно через главную страницу — https://central.sonatype.com/. Кнопка для регистрации справа сверху

После регистрации верифицируйте ваш email и войдите в аккаунт. Далее нужно верифицировать namespace — это будет ваша первая часть проекта. Например, в моем случае я владею доменом kulikov.uk, а значит я смогу использовать в качестве namespace uk.kulikov
Для верификации владения домена вам нужно добавить TXT запись в DNS. У Maven Central есть гайд как это сделать тут.
Если у вас нет своего домена, то вы сможете воспользоваться одним из публичнодоступных из списка:
-
Github:
io.github.myusername -
Gitlab:
io.gitlab.myusername -
Gitee:
io.gitee.myusername -
Bitbucket:
io.bitbucket.myusername
Всегда актуальную инструкцию по верификации namespace можно найти в документации Maven Central: https://central.sonatype.org/register/namespace/#for-code-hosting-services-with-personal-groupid
В результате на странице с namespace’ами у вас должен быть как минимум один верифицированный namespace как у меня на скриншоте:

Создания GPG ключа для подписи артефактов
Прежде чем приступать к загрузке артефакта необходимо его подписать своим ключем. Чтобы каждый, кто скачивает вашу библиотеку, мог быть уверен что это вы
-
Устанавливаем утилиту GPG
На MacOS с помощью brew: Введите в терминале brew install gnupg
На Linux с помощью apt-get: Введите в терминале sudo apt-get install gnupg
-
Генерируем PGP ключ:
В терминале вводим и затем заполняем все требуемые поля:
gpg --generate-key
Запомните или запишите passphrase, который использовали при настройке! Не используйте пустой!
-
Публикуем наш публичный ключ
Невероятно важно опубликовать публичный ключ на один из поддерживаемых Maven Central’ом серверов. Только так Central Portal сможет понять что артефакт пришел от вас.
Для публикации я решил использовать keyserver.ubuntu.com и опубликовал публичный ключ с помощью команды ниже.
gpg --keyserver keyserver.ubuntu.com --send-keys BF81E10590D4EBF590D00F911D41D36F7A67A07C
Замените BF81E10590D4EBF590D00F911D41D36F7A67A07C своим ID ключа из вывода команды gpg --generate-key. В случае если вы не нашли ID ключа, то можете получить все ваши ключи с помощью команды:
gpg --list-secret-keys --keyid-format LONG
Подойдут оба формата записи ID ключа — в моем случае это 1D41D36F7A67A07C и BF81E10590D4EBF590D00F911D41D36F7A67A07C
Локальная публикация Maven проекта
Конфигурация этого шага очень сильно зависит от того какой стек вы используете. Итоговым результатом будет опубликованный пакет в локальном maven-репозитории. Локальный Maven-репозиторий находится в:
-
Windows:
C:\Users\<User_Name>\.m2 -
Linux:
/home/<User_Name>/.m2 -
Mac:
/Users/<user_name>/.m2
-
Для публикации вам следует подключить Gradle Plugin
maven-publish:
plugins { ... id("maven-publish") ... }
-
Для дальнейшей публикации через JReleaser мы должны добавить публикацию в локальную папку в
buildпапке проекта
publishing { ... repositories { maven { setUrl(layout.buildDirectory.dir("staging-deploy")) } } }
-
Для Java проекта не забудьте добавить java-компонент:
publishing { publications { create<MavenPublication>("release") { from(components["java"]) ... } ... } ... }
-
Для Android проекта добавьте Android-компонент:
android { publishing { singleVariant("release") { withSourcesJar() withJavadocJar() } } } publishing { publications { create<MavenPublication>("release") { afterEvaluate { from(components["release"]) } ... } ... } ... }
Для примера вы можете взять настроенную Maven публикацию из моих репозиториев:
-
Пример для публикации Android-библиотеки можете найти тут
-
Пример для публикации чистой Java-библиотеки можете найти тут
Проверьте что ваша библиотека собрана и подключается корректно с помощью команды:
./gradlew publishToMavenLocal
Затем в другом вашем проекте добавьте в Maven repositories локальный репозиторий:
repositories { google() mavenCentral() ... mavenLocal() }
И можно подключать к проекту библиотеку. В моем случае для groupId = "uk.kulikov.detekt.decompose" , artifactId = "decompose-detekt-rules" добавление библиотеки выглядит так:
implementation("uk.kulikov.detekt.decompose:decompose-detekt-rules:0.1")
Подключение JReleaser к проекту и публикация локально
-
Добавляем JReleaser плагин к проекту. Инструкцию как это сделать можно найти тут
-
Извлекаем публичный и приватный ключ из хранилища. Замените
BF81E10590D4EBF590D00F911D41D36F7A67A07Cна свой ID ключа:
gpg --output public.pgp --armor --export BF81E10590D4EBF590D00F911D41D36F7A67A07C gpg --output private.pgp --armor --export-secret-key BF81E10590D4EBF590D00F911D41D36F7A67A07C
Эти команды создадут два файла — public.pgp и private.pgp
-
Генерируем токены доступа к Central Portal для публикации на странице аккаунта по кнопке “Generate User Token”:

-
Сгенерируем токен для GitHub. Это нужно для публикации релиза в GitHub. Если вы не хотите этого, пропустите этот шаг. Токен генерируется по этой ссылке: https://github.com/settings/tokens/new
Для публикации релиза нужен доступ на запись
-
Добавляем конфиг JReleaser со всеми необходимыми параметрами для публикации. Мы используем toml потому-что там удобнее указывать мультистрочные параметры, поэтому локально конфиг-файл храниться по пути
~/.jreleaser/config.toml. Мой конфиг выглядит примерно так (я вырезал свои токены в целях безопасности):
JRELEASER_GITHUB_TOKEN="EMPTY" JRELEASER_GPG_PASSPHRASE="supersecretpassword" JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN="Maven Central Portal Token" JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME="Maven Central Portal Password/Username" JRELEASER_GPG_PUBLIC_KEY="""-----BEGIN PGP PUBLIC KEY BLOCK----- ... -----END PGP PUBLIC KEY BLOCK-----""" JRELEASER_GPG_SECRET_KEY="""-----BEGIN PGP PRIVATE KEY BLOCK----- ... -----END PGP PRIVATE KEY BLOCK-----"""
-
Добавьте в проект минимальную конфигурацию jReleaser:
jreleaser { release { github { skipRelease = true skipTag = true } } }
-
Запустите
./gradlew jreleaserConfigчтобы проверить что все настроено правильно. Мой вывод выглядит так:
hooks: enabled: false active: NEVER command: enabled: false active: NEVER script: enabled: false active: NEVER project: name: detekt-decompose-rule version: 0.2 versionPattern: SEMVER snapshot: enabled: false pattern: .*-SNAPSHOT label: early-access fullChangelog: false description: Detekt ruleset for Decompose project longDescription: Detekt ruleset for Decompose project stereotype: NONE links: license: https://github.com/LionZXY/detekt-decompose-rule/blob/main/LICENSE bugTracker: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/issues vcsBrowser: https://{{repoHost}}/{{repoOwner}}/{{repoName}} extraProperties: versionMajor: 0 versionMinor: 2 versionNumber: 0.2 versionWithUnderscores: 0_2 versionWithDashes: 0-2 versionNumberWithUnderscores: 0_2 versionNumberWithDashes: 0-2 effectiveVersionWithUnderscores: 0_2 effectiveVersionWithDashes: 0-2 java: enabled: true version: 8 groupId: uk.kulikov.detekt.decompose artifactId: detekt-decompose-rule multiProject: false release: github: enabled: true host: github.com owner: LionZXY name: detekt-decompose-rule username: LionZXY token: ************ uploadAssets: ALWAYS artifacts: true files: true checksums: true catalogs: true signatures: true repoUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}} repoCloneUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}.git commitUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/commits srcUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/blob/{{repoBranch}} downloadUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/releases/download/{{tagName}}/{{artifactFile}} releaseNotesUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/releases/tag/{{tagName}} latestReleaseUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/releases/latest issueTrackerUrl: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/issues tagName: v{{projectVersion}} releaseName: Release {{tagName}} branch: main branchPush: main commitAuthor: name: jreleaserbot email: jreleaser@kordamp.org sign: false skipTag: false skipRelease: false overwrite: false update: enabled: false apiEndpoint: https://api.github.com connectTimeout: 20 readTimeout: 60 changelog: enabled: true append: enabled: false links: false skipMergeCommits: false formatted: NEVER hide: contributors: milestone: name: {{tagName}} close: true issues: enabled: false comment: ? This issue has been resolved in `{{tagName}}` ([Release Notes]({{releaseNotesUrl}})) label: name: released color: #FF0000 description: Issue has been released prerelease: enabled: false draft: false releaseNotes: enabled: false checksum: name: checksums.txt individual: false algorithms: SHA_256 artifacts: true files: true catalog: enabled: false active: NEVER sbom: enabled: false active: NEVER
-
Добавьте блок
signingдля подписи в Central Portal:
jreleaser { ... signing { active = Active.ALWAYS armored = true verify = true } }
Проверьте что подпись успешна с помощью команды: ./gradlew jreleaserSign
-
Добавьте как минимум одного автора и год публикации. Это нужно для генерации лицензии jReleser плагином:
jreleaser { project { inceptionYear = "2024" author("@LionZXY") } ... }
-
Добавляем публикацию в Maven Central:
jreleaser { ... deploy { maven { mavenCentral.create("sonatype") { active = Active.ALWAYS url = "https://central.sonatype.com/api/v1/publisher" stagingRepository(layout.buildDirectory.dir("staging-deploy").get().toString()) setAuthorization("Basic") } } } }
Если вы публикуете Android-проект, то вам придется выключить верификацию POM до исправления этого issue:
jreleaser { ... deploy { maven { mavenCentral.create("sonatype") { ... applyMavenCentralRules = false // Wait for fix: https://github.com/kordamp/pomchecker/issues/21 sign = true checksums = true sourceJar = true javadocJar = true ... } } } }
Таймаут по умолчанию слишком маленький, я увеличил его с помощью изменения параметра retryDelay :
jreleaser { ... deploy { maven { mavenCentral.create("sonatype") { ... retryDelay = 60 ... } } } }
Все! Готово! Мы можем сделать первую публикацию в Central Portal
Мой итоговый файл для Android-библиотеки выглядит в итоге так:
jreleaser { project { inceptionYear = "2024" author("@LionZXY") } gitRootSearch = true // I added this parameter because my project is in a subfolder signing { active = Active.ALWAYS armored = true verify = true } release { github { skipRelease = true skipTag = true } } deploy { maven { mavenCentral.create("sonatype") { active = Active.ALWAYS url = "https://central.sonatype.com/api/v1/publisher" stagingRepository(layout.buildDirectory.dir("staging-deploy").get().toString()) setAuthorization("Basic") applyMavenCentralRules = false // Wait for fix: https://github.com/kordamp/pomchecker/issues/21 sign = true checksums = true sourceJar = true javadocJar = true retryDelay = 60 } } } }
Процесс публикации теперь будет выглядеть так:
./gradlew jreleaserConfig build publish ./gradlew jreleaserFullRelease
Внимание: Убедитесь что в названии версии нет постфикса -SNAPSHOT !
Напоминаю что вы всегда можете свериться с уже настроенной публикацией из примеров:
Публикация в GitHub Actions
Для удобства публикации я предлагаю вам настроить автоматический CI/CD. Публикация будет происходить по назначению тега в репозитории.
Самый простой способ начать — просто скопировать файл ниже по пути .github/workflows/release.yml:
name: Publish to mavencentral on: push: tags: - '*' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK 21 uses: actions/setup-java@v2 with: java-version: '21' distribution: 'temurin' - name: Build and publish with Gradle uses: gradle/gradle-build-action@3 with: arguments: --no-daemon -i jreleaserConfig build test publish env: JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME }} JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN }} JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }} - name: Release with gradle uses: gradle/gradle-build-action@3 with: arguments: --no-daemon -i jreleaserFullRelease env: JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME }} JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN }} JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }}
Далее добавить в Settings → Secrets and Variables → Actions секреты для публикации:
-
JRELEASER_GPG_PASSPHRASE -
JRELEASER_GPG_PUBLIC_KEY -
JRELEASER_GPG_SECRET_KEY -
JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN -
JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME

Необходимо запушить все изменения в GitHub и попробовать создать тег — я делаю это через создание нового релиза

Если все пройдет хорошо, то в запущенных Actions появиться ваш run и спустя какое-то время вы сможете найти свою публикацию в Maven Central
https://central.sonatype.com/
Troubleshooting
-
При сборке Gradle ошибка типа
release.github.token must not be blank. Configure a value using the Gradle DSL, or define a System property jreleaser.github.token, or define a JRELEASER_GITHUB_TOKEN environment variable, or define a key/value pair in /Users/lionzxy/.jreleaser/config.toml with a key named JRELEASER_GITHUB_TOKEN
Решение: Проверьте что вы config файл задан верно или в переменных окружения есть этот параметр
-
В Central Portal ошибка типа:
Invalid signature for file: slf4j2-timber-0.1.pom
Решение: Проверьте что вы подписываете артефакт и загрузили его в keyserver.ubuntu.com
-
При публикации gpg ключа ошибка:
gpg: keyserver send failed: No route to host
Решение: Выполните команду host keyserver.ubuntu.com чтобы узнать IP адреса сервера Ubuntu:
keyserver.ubuntu.com has address 185.125.188.27 keyserver.ubuntu.com has address 185.125.188.26 keyserver.ubuntu.com has IPv6 address 2620:2d:4000:1007::d43 keyserver.ubuntu.com has IPv6 address 2620:2d:4000:1007::70c
Замените URL одним из IP. Например:
gpg --keyserver 185.125.188.27 --send-keys 1D41D36F7A67A07C
-
В Central Portal ошибка типа:
Invalid 'md5' checksum for file: slf4j2-timber-0.2-javadoc.jar.asc
Решение: Проверьте что вы подписываете ваши файлы с помощью команды ./gradlew jreleaserConfig . Вывод должен быть такой:
deploy: ... maven: mavenCentral: sonatype: .... sign: true checksums: true sourceJar: true javadocJar: true ...
Флаг applyMavenCentralRules = false автоматически отключает подпись. Поэтому подпись нужно включить насильно:
jreleaser { ... deploy { maven { mavenCentral.create("sonatype") { ... sign = true checksums = true sourceJar = true javadocJar = true ... } } } }
Или подписывать своими силами:
plugins { id("signing") } signing { val signingSecretKey = System.getenv("JRELEASER_GPG_SECRET_KEY") val signingPasskey = System.getenv("JRELEASER_GPG_PASSPHRASE") useInMemoryPgpKeys(signingSecretKey, signingPasskey) sign(publishing.publications.getByName("release")) }
-
При сборке jreleaser ошибка типа:
No release provider has been configured
Решение: Вам нужно иметь хотя бы один releaser — проще всего использовать GitHub
jreleaser { release { github { enabled = true } } }
-
При сборке jreleaser ошибка типа:
Could not determine git HEAD
Решение: Скорее всего вы пытаетесь выполнять конфигурацию jReleaser из сабдиректории. Для исправления этого вам нужно передать специальный флаг — gitRootSearch:
jreleaser { gitRootSearch = true }
-
При публикации ошибка:
<description> is not defined in POM. Will use value from parent:
Решение: Проверьте что вы корректно задали description в POM файле
-
Когда я ввожу
./gradlew jreleaserFullReleaseпубликации не происходит
Решение: Убедитесь что в названии вашей версии нет постфикса -SNAPSHOT
-
При публикации ошибка:
cannot be uploaded to Maven Central due to the following reasons: * <version> can not be -SNAPSHOT.
Решение: Если вы уже удалили постфикс -SNAPSHOT, то попробуйте выполнить ./gradlew clean
-
На любом этапе выполнения операций с gradle plugin jreleaser:
Execution failed for task ':jreleaserFullRelease'. > Unexpected error
Решение: Проверьте файл build/jreleaser/trace.log на наличие дополнительных ошибок
-
При публикации в
trace.logjreleaser ошибка 403.
Решение: Проверьте правильность credentials. Попробуйте поменять их местами
12) Version unspecified does not follow the semver spec
Решение: убедитесь что ваша версия следует https://semver.org/
Так же убедитесь что вы задали version внутри build.gradle вашего приложения:
version = "1.1"
ссылка на оригинал статьи https://habr.com/ru/articles/830630/

Добавить комментарий