# The Maven Publish Plugin

version 9.3.1

The Maven Publish Plugin provides the ability to publish build artifacts to an Apache Maven repository. A module published to a Maven repository can be consumed by Maven, Gradle (see Declaring Dependencies) and other tools that understand the Maven repository format. You can learn about the fundamentals of publishing in Publishing Overview.

## Usage

To use the Maven Publish Plugin, include the following in your build script:

Example 1. Applying the Maven Publish Plugin
build.gradle.kts

    plugins {
        `maven-publish`
    }

build.gradle

    plugins {
        id 'maven-publish'
    }

The Maven Publish Plugin uses an extension on the project named `publishing` of type PublishingExtension. This extension provides a container of named publications and a container of named repositories. The Maven Publish Plugin works with MavenPublication publications and MavenArtifactRepository repositories.

## Tasks

`generatePomFileForPubNamePublication` — GenerateMavenPom

  Creates a POM file for the publication named PubName, populating the known metadata such as project name, project version, and the dependencies. The default location for the POM file is build/publications/$pubName/pom-default.xml.

`publishPubNamePublicationToRepoNameRepository` — PublishToMavenRepository

  Publishes the PubName publication to the repository named RepoName. If you have a repository definition without an explicit name, RepoName will be "Maven".

`publishPubNamePublicationToMavenLocal` — PublishToMavenLocal

  Copies the PubName publication to the local Maven cache — typically <home directory of the current user>/.m2/repository — along with the publication’s POM file and other metadata.

`publish`

  Depends on: All `publishPubNamePublicationToRepoNameRepository` tasks

  An aggregate task that publishes all defined publications to all defined repositories. It does not include copying publications to the local Maven cache.

`publishToMavenLocal`

  Depends on: All `publishPubNamePublicationToMavenLocal` tasks

  Copies all defined publications to the local Maven cache, including their metadata (POM files, etc.).

## Publications

This plugin provides publications of type MavenPublication. To learn how to define and use publications, see the section on basic publishing.

There are four main things you can configure in a Maven publication:

- A component — via MavenPublication.from(org.gradle.api.component.SoftwareComponent).

- Custom artifacts — via the MavenPublication.artifact(java.lang.Object) method. See MavenArtifact for the available configuration options for custom Maven artifacts.

- Standard metadata like `artifactId`, `groupId` and `version`.

- Other contents of the POM file — via MavenPublication.pom(org.gradle.api.Action).

You can see all of these in action in the complete publishing example. The API documentation for `MavenPublication` has additional code samples.

### Identity values in the generated POM

The attributes of the generated POM file will contain identity values derived from the following project properties:

- `groupId` - Project.getGroup()

- `artifactId` - Project.getName()

- `version` - Project.getVersion()

Overriding the default identity values is easy: simply specify the `groupId`, `artifactId` or `version` attributes when configuring the MavenPublication.

Example 2. Customizing the publication identity
build.gradle.kts

    publishing {
        publications {
            create<MavenPublication>("maven") {
                groupId = "org.gradle.sample"
                artifactId = "library"
                version = "1.1"

                from(components["java"])
            }
        }
    }

build.gradle

    publishing {
        publications {
            maven(MavenPublication) {
                groupId = 'org.gradle.sample'
                artifactId = 'library'
                version = '1.1'

                from components.java
            }
        }
    }

| Certain repositories will not be able to handle all supported characters. For example, the `:` character cannot be used as an identifier when publishing to a filesystem-backed repository on Windows.

Maven restricts `groupId` and `artifactId` to a limited character set (`[A-Za-z0-9_\\-.]+`) and Gradle enforces this restriction. For `version` (as well as the artifact `extension` and `classifier` properties), Gradle will handle any valid Unicode character.

The only Unicode values that are explicitly prohibited are `\`, `/` and any ISO control character. Supplied values are validated early in publication.

### Customizing the generated POM

The generated POM file can be customized before publishing. For example, when publishing a library to Maven Central you will need to set certain metadata. The Maven Publish Plugin provides a DSL for that purpose. Please see MavenPom in the DSL Reference for the complete documentation of available properties and methods. The following sample shows how to use the most common ones:

Example 3. Customizing the POM file
build.gradle.kts

    publishing {
        publications {
            create<MavenPublication>("mavenJava") {
                pom {
                    name = "My Library"
                    description = "A concise description of my library"
                    url = "http://www.example.com/library"
                    properties = mapOf(
                        "myProp" to "value",
                        "prop.with.dots" to "anotherValue"
                    )
                    licenses {
                        license {
                            name = "The Apache License, Version 2.0"
                            url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
                        }
                    }
                    developers {
                        developer {
                            id = "johnd"
                            name = "John Doe"
                            email = "john.doe@example.com"
                        }
                    }
                    scm {
                        connection = "scm:git:git://example.com/my-library.git"
                        developerConnection = "scm:git:ssh://example.com/my-library.git"
                        url = "http://example.com/my-library/"
                    }
                }
            }
        }
    }

build.gradle

    publishing {
        publications {
            mavenJava(MavenPublication) {
                pom {
                    name = 'My Library'
                    description = 'A concise description of my library'
                    url = 'http://www.example.com/library'
                    properties = [
                        myProp: "value",
                        "prop.with.dots": "anotherValue"
                    ]
                    licenses {
                        license {
                            name = 'The Apache License, Version 2.0'
                            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                        }
                    }
                    developers {
                        developer {
                            id = 'johnd'
                            name = 'John Doe'
                            email = 'john.doe@example.com'
                        }
                    }
                    scm {
                        connection = 'scm:git:git://example.com/my-library.git'
                        developerConnection = 'scm:git:ssh://example.com/my-library.git'
                        url = 'http://example.com/my-library/'
                    }
                }
            }
        }
    }

### Customizing dependencies versions

Two strategies are supported for publishing dependencies:

Declared versions (default)

  This strategy publishes the versions that are defined by the build script author with the dependency declarations in the `dependencies` block. Any other kind of processing, for example through a rule changing the resolved version, will not be taken into account for the publication.

Resolved versions

  This strategy publishes the versions that were resolved during the build, possibly by applying resolution rules and automatic conflict resolution. This has the advantage that the published versions correspond to the ones the published artifact was tested against.

Example use cases for resolved versions:

- A project uses dynamic versions for dependencies but prefers exposing the resolved version for a given release to its consumers.

- In combination with dependency locking, you want to publish the locked versions.

- A project leverages the rich versions constraints of Gradle, which have a lossy conversion to Maven. Instead of relying on the conversion, it publishes the resolved versions.

This is done by using the `versionMapping` DSL method which allows to configure the VersionMappingStrategy:

Example 4. Using resolved versions
build.gradle.kts

    publishing {
        publications {
            create<MavenPublication>("mavenJava") {
                versionMapping {
                    usage("java-api") {
                        fromResolutionOf("runtimeClasspath")
                    }
                    usage("java-runtime") {
                        fromResolutionResult()
                    }
                }
            }
        }
    }

build.gradle

    publishing {
        publications {
            mavenJava(MavenPublication) {
                versionMapping {
                    usage('java-api') {
                        fromResolutionOf('runtimeClasspath')
                    }
                    usage('java-runtime') {
                        fromResolutionResult()
                    }
                }
            }
        }
    }

In the example above, Gradle will use the versions resolved on the `runtimeClasspath` for dependencies declared in `api`, which are mapped to the `compile` scope of Maven. Gradle will also use the versions resolved on the `runtimeClasspath` for dependencies declared in `implementation`, which are mapped to the `runtime` scope of Maven. `fromResolutionResult()` indicates that Gradle should use the default classpath of a variant and `runtimeClasspath` is the default classpath of `java-runtime`.

## Repositories

This plugin provides repositories of type MavenArtifactRepository. To learn how to define and use repositories for publishing, see the section on basic publishing.

Here’s a simple example of defining a publishing repository:

Example 5. Declaring repositories to publish to
build.gradle.kts

    publishing {
        repositories {
            maven {
                // change to point to your repo, e.g. http://my.org/repo
                url = uri(layout.buildDirectory.dir("repo"))
            }
        }
    }

build.gradle

    publishing {
        repositories {
            maven {
                // change to point to your repo, e.g. http://my.org/repo
                url = layout.buildDirectory.dir('repo')
            }
        }
    }

The two main things you will want to configure are the repository’s:

- URL (required)

- Name (optional)

You can define multiple repositories as long as they have unique names within the build script. You may also declare one (and only one) repository without a name. That repository will take on an implicit name of "Maven".

You can also configure any authentication details that are required to connect to the repository. See MavenArtifactRepository for more details.

### Supported Repositories

Gradle’s `maven-publish` plugin is compatible with the Maven Deploy Plugin and supports publishing to any repository that adheres to the same standards and protocols. This includes a wide range of Maven-compatible repositories, such as:

- Local repositories (e.g. `~/.m2/repository`)

- Custom remote Maven repositories

- Repository managers like Sonatype Nexus and JFrog Artifactory

- GitHub Packages

- Internal corporate Maven repositories

### Publishing to Maven Central

The service commonly known as Maven Central and hosted at central.sonatype.com is a widely used repository for distributing Java and Kotlin libraries.

As of June 30, 2025, Maven Central no longer supports the legacy deployment protocol used by the Maven Deploy Plugin. This change also marks the deprecation of the OSSRH (OSS Repository Hosting) service.

Going forward, publishing to Maven Central requires a dedicated plugin. Several options are available. For guidance and recommendations, see the Publishing to Maven Central page in the Gradle Cookbook.

### Snapshot and release repositories

It is a common practice to publish snapshots and releases to different Maven repositories. A simple way to accomplish this is to configure the repository URL based on the project version. The following sample uses one URL for versions that end with "SNAPSHOT" and a different URL for the rest:

Example 6. Configuring repository URL based on project version
build.gradle.kts

    publishing {
        repositories {
            maven {
                val releasesRepoUrl = layout.buildDirectory.dir("repos/releases")
                val snapshotsRepoUrl = layout.buildDirectory.dir("repos/snapshots")
                url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
            }
        }
    }

build.gradle

    publishing {
        repositories {
            maven {
                def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
                def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
                url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
            }
        }
    }

Similarly, you can use a project or system property to decide which repository to publish to. The following example uses the release repository if the project property `release` is set, such as when a user runs `gradle -Prelease publish`:

Example 7. Configuring repository URL based on project property
build.gradle.kts

    publishing {
        repositories {
            maven {
                val releasesRepoUrl = layout.buildDirectory.dir("repos/releases")
                val snapshotsRepoUrl = layout.buildDirectory.dir("repos/snapshots")
                url = uri(if (project.hasProperty("release")) releasesRepoUrl else snapshotsRepoUrl)
            }
        }
    }

build.gradle

    publishing {
        repositories {
            maven {
                def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
                def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
                url = project.hasProperty('release') ? releasesRepoUrl : snapshotsRepoUrl
            }
        }
    }

## Publishing to Maven Local

For integration with a local Maven installation, it is sometimes useful to publish the module into the Maven local repository (typically at <home directory of the current user>/.m2/repository), along with its POM file and other metadata. In Maven parlance, this is referred to as 'installing' the module.

The Maven Publish Plugin makes this easy to do by automatically creating a PublishToMavenLocal task for each MavenPublication in the `publishing.publications` container. The task name follows the pattern of `publishPubNamePublicationToMavenLocal`. Each of these tasks is wired into the `publishToMavenLocal` aggregate task. You do not need to have `mavenLocal()` in your `publishing.repositories` section.

## Publishing to Maven Central

## Publishing Maven relocation information

When a project changes the `groupId` or `artifactId` (the coordinates) of an artifact it publishes, it is important to let users know where the new artifact can be found. Maven can help with that through the relocation feature. The way this works is that a project publishes an additional artifact under the old coordinates consisting only of a minimal relocation POM; that POM file specifies where the new artifact can be found. Maven repository browsers and build tools can then inform the user that the coordinates of an artifact have changed.

For this, a project adds an additional `MavenPublication` specifying a MavenPomRelocation:

Example 8. Specifying a relocation POM
build.gradle.kts

    publishing {
        publications {
            // ... artifact publications

            // Specify relocation POM
            create<MavenPublication>("relocation") {
                pom {
                    // Old artifact coordinates
                    groupId = "com.example"
                    artifactId = "lib"
                    version = "2.0.0"

                    distributionManagement {
                        relocation {
                            // New artifact coordinates
                            groupId = "com.new-example"
                            artifactId = "lib"
                            version = "2.0.0"
                            message = "groupId has been changed"
                        }
                    }
                }
            }
        }
    }

build.gradle

    publishing {
        publications {
            // ... artifact publications

            // Specify relocation POM
            relocation(MavenPublication) {
                pom {
                    // Old artifact coordinates
                    groupId = "com.example"
                    artifactId = "lib"
                    version = "2.0.0"

                    distributionManagement {
                        relocation {
                            // New artifact coordinates
                            groupId = "com.new-example"
                            artifactId = "lib"
                            version = "2.0.0"
                            message = "groupId has been changed"
                        }
                    }
                }
            }
        }
    }

Only the property which has changed needs to be specified under `relocation`, that is `artifactId` and / or `groupId`. All other properties are optional.

| Specifying the `version` can be useful when the new artifact has a different version, for example because version numbering has started at 1.0.0 again. A custom `message` allows explaining why the artifact coordinates have changed.

The relocation POM should be created for what would be the next version of the old artifact. For example when the artifact coordinates of `com.example:lib:1.0.0` are changed and the artifact with the new coordinates continues version numbering and is published as `com.new-example:lib:2.0.0`, then the relocation POM should specify a relocation from `com.example:lib:2.0.0` to `com.new-example:lib:2.0.0`.

A relocation POM only has to be published once, the build file configuration for it should be removed again once it has been published.

Note that a relocation POM is not suitable for all situations; when an artifact has been split into two or more separate artifacts then a relocation POM might not be helpful.

### Retroactively publishing relocation information

It is possible to publish relocation information retroactively after the coordinates of an artifact have changed in the past, and no relocation information was published back then.

The same recommendations as described above apply. To ease migration for users, it is important to pay attention to the `version` specified in the relocation POM. The relocation POM should allow the user to move to the new artifact in one step, and then allow them to update to the latest version in a separate step. For example when for the coordinates of `com.new-example:lib:5.0.0` were changed in version 2.0.0, then ideally the relocation POM should be published for the old coordinates `com.example:lib:2.0.0` relocating to `com.new-example:lib:2.0.0`. The user can then switch from `com.example:lib` to `com.new-example` and then separately update from version 2.0.0 to 5.0.0, handling breaking changes (if any) step by step.

When relocation information is published retroactively, it is not necessary to wait for next regular release of the project, it can be published in the meantime. As mentioned above, the relocation information should then be removed again from the build file once the relocation POM has been published.

### Avoiding duplicate dependencies

When only the coordinates of the artifact have changed, but package names of the classes inside the artifact have remained the same, dependency conflicts can occur. A project might (transitively) depend on the old artifact but at the same time also have a dependency on the new artifact which both contain the same classes, potentially with incompatible changes.

To detect such conflicting duplicate dependencies, capabilities can be published as part of the Gradle Module Metadata. For an example using a Java Library project, see declaring additional capabilities for a local component.

### Performing a dry run

To verify that relocation information works as expected before publishing it to a remote repository, it can first be published to the local Maven repository. Then a local test Gradle or Maven project can be created which has the relocation artifact as dependency.

## Complete example

The following example demonstrates how to sign and publish a Java library including sources, Javadoc, and a customized POM:

Example 9. Publishing a Java library
build.gradle.kts

    plugins {
        `java-library`
        `maven-publish`
        signing
    }

    group = "com.example"
    version = "1.0"

    java {
        withJavadocJar()
        withSourcesJar()
    }

    publishing {
        publications {
            create<MavenPublication>("mavenJava") {
                artifactId = "my-library"
                from(components["java"])
                versionMapping {
                    usage("java-api") {
                        fromResolutionOf("runtimeClasspath")
                    }
                    usage("java-runtime") {
                        fromResolutionResult()
                    }
                }
                pom {
                    name = "My Library"
                    description = "A concise description of my library"
                    url = "http://www.example.com/library"
                    properties = mapOf(
                        "myProp" to "value",
                        "prop.with.dots" to "anotherValue"
                    )
                    licenses {
                        license {
                            name = "The Apache License, Version 2.0"
                            url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
                        }
                    }
                    developers {
                        developer {
                            id = "johnd"
                            name = "John Doe"
                            email = "john.doe@example.com"
                        }
                    }
                    scm {
                        connection = "scm:git:git://example.com/my-library.git"
                        developerConnection = "scm:git:ssh://example.com/my-library.git"
                        url = "http://example.com/my-library/"
                    }
                }
            }
        }
        repositories {
            maven {
                // change URLs to point to your repos, e.g. http://my.org/repo
                val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases"))
                val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots"))
                url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
            }
        }
    }

    signing {
        sign(publishing.publications["mavenJava"])
    }

    tasks.javadoc {
        if (JavaVersion.current().isJava9Compatible) {
            (options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
        }
    }

build.gradle

    plugins {
        id 'java-library'
        id 'maven-publish'
        id 'signing'
    }

    group = 'com.example'
    version = '1.0'

    java {
        withJavadocJar()
        withSourcesJar()
    }

    publishing {
        publications {
            mavenJava(MavenPublication) {
                artifactId = 'my-library'
                from components.java
                versionMapping {
                    usage('java-api') {
                        fromResolutionOf('runtimeClasspath')
                    }
                    usage('java-runtime') {
                        fromResolutionResult()
                    }
                }
                pom {
                    name = 'My Library'
                    description = 'A concise description of my library'
                    url = 'http://www.example.com/library'
                    properties = [
                        myProp: "value",
                        "prop.with.dots": "anotherValue"
                    ]
                    licenses {
                        license {
                            name = 'The Apache License, Version 2.0'
                            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                        }
                    }
                    developers {
                        developer {
                            id = 'johnd'
                            name = 'John Doe'
                            email = 'john.doe@example.com'
                        }
                    }
                    scm {
                        connection = 'scm:git:git://example.com/my-library.git'
                        developerConnection = 'scm:git:ssh://example.com/my-library.git'
                        url = 'http://example.com/my-library/'
                    }
                }
            }
        }
        repositories {
            maven {
                // change URLs to point to your repos, e.g. http://my.org/repo
                def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
                def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
                url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
            }
        }
    }

    signing {
        sign publishing.publications.mavenJava
    }

    javadoc {
        if(JavaVersion.current().isJava9Compatible()) {
            options.addBooleanOption('html5', true)
        }
    }

The result is that the following artifacts will be published:

- The POM: `my-library-1.0.pom`

- The primary JAR artifact for the Java component: `my-library-1.0.jar`

- The sources JAR artifact that has been explicitly configured: `my-library-1.0-sources.jar`

- The Javadoc JAR artifact that has been explicitly configured: `my-library-1.0-javadoc.jar`

The Signing Plugin is used to generate a signature file for each artifact. In addition, checksum files will be generated for all artifacts and signature files.

| publishToMavenLocal` does not create checksum files in `$USER_HOME/.m2/repository`. If you want to verify that the checksum files are created correctly, or use them for later publishing, consider configuring a custom Maven repository with a `file://` URL and using that as the publishing target instead.

## Removal of deferred configuration behavior

Prior to Gradle 5.0, the `publishing {}` block was (by default) implicitly treated as if all the logic inside it was executed after the project is evaluated. This behavior caused quite a bit of confusion and was deprecated in Gradle 4.8, because it was the only block that behaved that way.

You may have some logic inside your publishing block or in a plugin that is depending on the deferred configuration behavior. For instance, the following logic assumes that the subprojects will be evaluated when the artifactId is set:

build.gradle.kts

    subprojects {
        publishing {
            publications {
                create<MavenPublication>("mavenJava") {
                    from(components["java"])
                    artifactId = tasks.jar.get().archiveBaseName.get()
                }
            }
        }
    }

build.gradle

    subprojects {
        publishing {
            publications {
                mavenJava(MavenPublication) {
                    from components.java
                    artifactId = jar.archiveBaseName
                }
            }
        }
    }

This kind of logic must now be wrapped in an `afterEvaluate {}` block.

build.gradle.kts

    subprojects {
        publishing {
            publications {
                create<MavenPublication>("mavenJava") {
                    from(components["java"])
                    afterEvaluate {
                        artifactId = tasks.jar.get().archiveBaseName.get()
                    }
                }
            }
        }
    }

build.gradle

    subprojects {
        publishing {
            publications {
                mavenJava(MavenPublication) {
                    from components.java
                    afterEvaluate {
                        artifactId = jar.archiveBaseName
                    }
                }
            }
        }
    }