Guide for troubleshooting and configuring Maven Central publishing via Sonatype Central Portal. Use when the user encounters publishing failures, 402/401/404 errors from Sonatype, or needs to migrate from legacy OSSRH to the new Central Portal.
The legacy OSSRH endpoints (oss.sonatype.org and s01.oss.sonatype.org) have been shut down. All publishing must go through the new Central Portal at central.sonatype.com.
Common symptoms of needing this migration:
402 Payment Required from s01.oss.sonatype.org404 Not Found from central.sonatype.com/api/v1/publisher/401 Unauthorized from ossrh-staging-api.central.sonatype.comUse io.github.gradle-nexus.publish-plugin version 2.0.0+ in the root build.gradle:
plugins {
id("io.github.gradle-nexus.publish-plugin") version "2.0.0"
}
The staging API compatibility layer — NOT the Portal REST API:
| Setting | URL |
|---|---|
nexusUrl | https://ossrh-staging-api.central.sonatype.com/service/local/ |
snapshotRepositoryUrl | https://central.sonatype.com/repository/maven-snapshots/ |
WRONG URLs (do NOT use):
https://s01.oss.sonatype.org/... (legacy, returns 402)https://central.sonatype.com/api/v1/publisher/ (Portal REST API, returns 404 for staging plugin)nexusPublishing {
packageGroup.set("<group-id>") // e.g., "com.burhanrashid52"
repositories {
sonatype {
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
username.set(providers.gradleProperty('ossrhUsernameV2'))
password.set(providers.gradleProperty('ossrhPasswordV2'))
}
}
}
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --max-workers 1 \
-PossrhUsernameV2=$OSSRH_USERNAME_V2 \
-PossrhPasswordV2=$OSSRH_PASSWORD_V2
-P), Not Just Env VarsSystem.getenv() and even providers.environmentVariable() can fail silently when the nexusPublishing block is in the root build.gradle. The reliable approach is:
-P properties on the command lineproviders.gradleProperty() in the build scriptpackageGroup Is RequiredWithout packageGroup.set("<your-group-id>"), the plugin cannot resolve the correct staging profile and authentication may fail with a 401.
When migrating, remove:
io.codearte.nexus-staging plugin and its classpath dependencycom.github.dcendents:android-maven-gradle-plugin (if unused)nexusStaging { ... } block from publish scriptsrepositories { maven { ... } } block from publish scripts (the nexus plugin handles this)sonatypeStagingProfileId references (no longer needed)Credentials must be user tokens generated from Central Portal (central.sonatype.com > Account > Generate User Token), NOT login email/password.
curl -u "TOKEN_USERNAME:TOKEN_PASSWORD" \
-H "Accept: application/json" \
"https://ossrh-staging-api.central.sonatype.com/service/local/staging/profiles"
When publish fails, check in order:
ossrh-staging-api.central.sonatype.com URLpackageGroup set?-P Gradle properties?SIGNING_* env vars are setpublishToSonatype (not publishReleasePublicationToSonatypeRepository)| File | Changes |
|---|---|
build.gradle (root) | Remove old plugin, add nexus-publish-plugin, add nexusPublishing block |
scripts/publish-mavencentral.gradle | Remove repositories {} and nexusStaging {} blocks, remove old credential refs |
.github/workflows/publish_maven.yml | Update gradle command, pass -P properties, update secret names |