Skip to content

Migrate Maven Central publishing from legacy OSSRH to Central Portal #478

@hopeman15

Description

@hopeman15

Problem

Publishing to Maven Central is currently failing with:

Failed to publish publication 'release' to repository 'MavenCentral'
> Could not PUT 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/hellocuriosity/curiosity/1.0.0/curiosity-1.0.0.aar'.
  Received status code 402 from server: Payment Required

Sonatype sunset the legacy OSSRH endpoints (oss.sonatype.org / s01.oss.sonatype.org) on 30 June 2025. Requests to the old Nexus staging API now return 401/402. Our gradle/publishing.gradle.kts still targets https://s01.oss.sonatype.org/service/local/staging/deploy/maven2, which is why releases are blocked.

Proposed fix

Migrate to the Central Publisher Portal using the Vanniktech Maven publish plugin, which speaks the new Portal API natively and removes most of our hand-rolled publishing config.

Code changes

Replace the body of gradle/publishing.gradle.kts with the Vanniktech plugin's DSL:

plugins {
    id(\"com.vanniktech.maven.publish\")
}

mavenPublishing {
    publishToMavenCentral()
    signAllPublications()

    coordinates(\"io.github.hellocuriosity\", artifactId, project.version.toString())

    pom {
        name.set(publicationName)
        description.set(publicationDescription)
        url.set(\"https://github.com/HelloCuriosity/curiosity\")
        licenses {
            license {
                name.set(\"MIT Licence\")
                url.set(\"https://github.com/HelloCuriosity/curiosity/blob/main/LICENSE\")
            }
        }
        developers {
            developer { id.set(\"hopeman15\"); name.set(\"Kyle Roe\") }
        }
        scm {
            connection.set(\"scm:git:https://github.com/HelloCuriosity/curiosity.git\")
            developerConnection.set(\"scm:git:https://github.com/HelloCuriosity/curiosity.git\")
            url.set(\"https://github.com/HelloCuriosity/curiosity\")
        }
    }
}

Register the plugin in the root build.gradle.kts:

plugins {
    id(\"com.vanniktech.maven.publish\") version \"0.30.0\" apply false
}

The plugin handles Android library component selection, sources/javadoc jars, and signing — so the current afterEvaluate / plugins.hasPlugin(\"com.android.library\") branching can be deleted.

CI / secrets changes

Generate a User Token at https://central.sonatype.com/account and replace the GitHub Actions secrets accordingly. The plugin reads from these Gradle properties (set via env in CI):

Old secret New env var
SONATYPE_USER ORG_GRADLE_PROJECT_mavenCentralUsername
SONATYPE_PWD ORG_GRADLE_PROJECT_mavenCentralPassword
SIGNING_KEY ORG_GRADLE_PROJECT_signingInMemoryKey
SIGNING_PWD ORG_GRADLE_PROJECT_signingInMemoryKeyPassword

CI task stays ./gradlew publishToMavenCentral (or switch to publishAndReleaseToMavenCentral to skip the Portal staging step).

Alternative considered

Sonatype provides a temporary OSSRH-compat shim at https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ that accepts the legacy Nexus protocol. Switching the URL is a one-liner, but the shim is explicitly a migration aid and will be retired — so it would just defer the same migration work.

Acceptance criteria

  • gradle/publishing.gradle.kts no longer references s01.oss.sonatype.org
  • User token generated at central.sonatype.com and added to repo secrets
  • io.github.hellocuriosity namespace verified on the Central Portal
  • A release publishes successfully end-to-end and appears on Maven Central

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions