Blog Infos
Author
Published
Topics
, , , ,
Published

Image Source — GPT 4o

Introduction

When developing an Android or Kotlin Multiplatform (KMM) library, it’s often helpful to test it in a real-world scenario before publishing it to a public repository like Maven Central. One of the quickest ways to do this is by utilizing Gradle’s publishToMavenLocal task, which publishes your library artifacts to your machine’s local Maven repository. This setup makes it easy to iterate and verify changes in a separate test or sample project—such as a Compose Multiplatform sample app—without waiting for a full release process.

A significant advantage of this local publishing approach is its simplicity:

You bypass the need to configure potentially complex remote repository solutions and, importantly, you do not need to GPG sign your artifacts, which is mandatory for public releases to repositories like Maven Central.

This makes iterating purely for local testing much faster.

Article Focus

This article focuses primarily on the publishToMavenLocal workflow and also introduces Composite Builds as an alternative method for even faster iteration.

In this guide, you’ll learn two approaches:

1. Publishing to Maven Local: Quickly publish your library to your local Maven repository and then consume it in another project by adding mavenLocal() to your dependency resolution. There are two common approaches to publishing to Maven Local:

  • Using the Standard maven‑publish Plugin
  • Using the com.vanniktech.maven.publish Plugin

2. Composite Builds: Set up a single workspace-style setup so that your test project and library live side-by-side, and changes are instantly available without repeatedly publishing.

We’ll cover configurations for both Android-only and Kotlin Multiplatform libraries, as the publishing setup differs slightly between these scenarios.

Choosing Your Library’s Maven-Local Publishing Setup for Local Testing

Before you can run publishToMavenLocal, your library project needs some basic configuration to define its identity (Maven coordinates) and tell Gradle what to publish. There are two main approaches to setting this up in your library’s build.gradle.kts:

Maven-Local Approach 1: Using the Standard maven‑publish Plugin
  • What it Does: Leverages Gradle’s built‑in maven‑publish plugin. You manually define MavenPublication objects within a publishing { publications { ... } } block, specifying their coordinates and contents (often derived from Gradle components like components["release"] for Android or using the default kotlinMultiplatform publication for KMP).
  • Why Choose This: This is the most basic method requiring no extra plugins. It’s sufficient if your only goal is quick, temporary local testing via publishToMavenLocal and you don’t have immediate plans for a public release, or you prefer to manage your release configuration completely separately later.
  • DSL Block Used: publishing { ... }
Maven-Local Approach 2: Using the com.vanniktech.maven.publish Plugin (Recommended)
  • What it Does: Uses a popular helper plugin (com.vanniktech.maven.publish) specifically designed to simplify publishing, especially to Maven Central. It automatically handles much of the boilerplate for finding publications (like kotlinMultiplatform or the Android release component), configuring standard POM details, and setting up signing or repository targets when needed for release. You configure everything via its dedicated mavenPublishing { ... } block.
  • Why Choose This: Generally recommended because it provides a single, consistent configuration that works perfectly for publishToMavenLocal (without needing signing) and easily scales to a full Maven Central release later by simply adding options like signAllPublications() and publishToMavenCentral(...) to the same mavenPublishing block. It avoids having different setups for local vs. remote publishing. It requires adding one external plugin.
  • DSL Block Used: mavenPublishing { ... }
Maven-Local Approach 1: Using the Standard maven‑publish Plugin

In your library’s main build.gradle.kts (or build.gradle if using Groovy), define the groupversion, and apply the maven-publish plugin. For example:

For a pure Android Library:

plugins {
    ...
    ...
    id("maven-publish") // <-- add this
}

// Provide coordinates for your artifact
group = "com.yourdomain"
version = "0.0.1"

android {
    publishing {
        ...
        ...
        // Tell Android to publish the release variant
        singleVariant("release")
    }
}
// Set up publishing
publishing {
    publications {
        register<MavenPublication>("release") {
            groupId = "com.yourdomain"
            artifactId = "my-awesome-library"
            version = "0.0.1"
            // Wait for Android to finish configuration
            afterEvaluate {
                from(components["release"])
            }
        }
    }
    repositories {
        mavenLocal()
    }
}

This creates a release publication for your Android library and publishes an .aar file along with a .pom and metadata files.

For a Kotlin Multiplatform Library:

A Kotlin Multiplatform project generates a kotlinMultiplatform publication that contains all supported targets (Android, iOS, etc.), along with common and native artifacts. You just need to set your Maven coordinates on that publication:

The following is targeting Kotlin Multiplatform libraries.

plugins {
    ...
    ...
    id("maven-publish") // <-- add this
}

// Provide coordinates for your artifact
group = "com.yourdomain"
version = "0.0.1"

// Set up publishing
publishing {
    publications {
        val kotlinMultiplatformPublication = publications.getByName("kotlinMultiplatform") as MavenPublication
        kotlinMultiplatformPublication.groupId = "com.yourdomain"
        kotlinMultiplatformPublication.artifactId = "my-awesome-library"
        kotlinMultiplatformPublication.version = "0.0.1"
    }
    repositories {
        mavenLocal()
    }
}

Key Points:

  • For Android libraries: You typically configure a release publication that comes from components["release"].
  • For KMM libraries: You rely on the existing kotlinMultiplatform publication. All platform-specific artifacts (Android .jar, iOS .klib, metadata .jar) are published together.
  • Group and Version: These define the Maven coordinates (groupId:artifactId:version) your consumer project will use.
Maven-Local Approach 2: Using the com.vanniktech.maven.publish Plugin

The idea is similar to the first approach, but using the vanniktech plugin, makes it very easy to switch between publishing to Maven Central and Maven Local.

at libs.versions.toml

[plugins]
# ...
# ...
# ...

vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.29.0" }

at the project’s build.gradle.kts

plugins {
    alias(libs.plugins.androidLibrary).apply(false)
    alias(libs.plugins.kotlinMultiplatform).apply(false)
    alias(libs.plugins.vanniktech.mavenPublish) apply false // <-- THIS
}

at the library module’s build.gradle.kts

// In your library's build.gradle.kts
plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.vanniktech.mavenPublish) // <-- THIS
    alias(libs.plugins.androidLibrary)
    alias(libs.plugins.kotlin.serialization)
}

group = "com.yourdomain" // Replace
version = "0.0.1"        // Replace

...
...
...

kotlin {
    androidTarget {
        ...
        ...
        publishLibraryVariants("release", "debug") // <-- THIS
        ...
        ...
    }
}


mavenPublishing {
    publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)

    // If you have created signing for publishing, enable the next line
    //signAllPublications()
    
    coordinates(
        groupId =  group.toString(),
        artifactId = "my-awesome-library",
        version = version.toString()
    )

    // sample POM
    pom {
        name = "My Awesome Library"
        description = "Library to do awesome stuff"
        inceptionYear = "2025"
        url = "https://github.com/my-profile/my-library"
        licenses {
            license {
                name = "MIT"
                url = "https://opensource.org/licenses/MIT"
            }
        }
        developers {
            developer {
                id = "ioannis-anifantakis"
                name = "Ioannis Anifantakis"
                url = "https://anifantakis.eu"
                email = "ioannisanif@gmail.com"
            }
        }
        scm {
            url = "https://github.com/my-profile/my-library"
            connection = "scm:git:https://github.com/my-profile/my-library.git"
            developerConnection = "scm:git:ssh://git@github.com/my-profile/my-library.git"
        }
    }
}

task("testClasses") {} // <-- THIS if you get errors
How about signing?

I can recommend a very good video by Stevdza-San to see how you can sign a library and publish it to Maven-Central!

Common Steps

No matter if you selected the Approach-1 or Approach-2, the next steps are common:

Step 1: Publish to Maven Local

Run the following command in the root directory of your library project:

./gradlew publishToMavenLocal --no-configuration-cache

This will compile and publish your library to ~/.m2/repository by default, placing it in a Maven-compatible structure. For instance:

~/.m2/repository/com/yourdomain/my-awesome-library/0.0.1/
  • Android Library Example: You’ll see .aar.pom.module, and possibly .jar files.
  • KMM Library Example: You’ll see a .metadata.jar.klib (for iOS), .pom.module, and possibly .jar for JVM/Android. The .module file is key, as it tells Gradle how to map each platform in your consumer project to the correct artifact. You won’t necessarily see separate files named specifically for each iOS platform, but the .klib combined with metadata informs Gradle how to handle iOS targets.
Step 2: Consuming the Locally Published Library in a Test Application

To test the library in another project (such as a sample app), you need to instruct that project to look in your local Maven repository.

In your test project’s settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        google()        
        mavenCentral()  
        mavenLocal()    // Add this line to include your local Maven repo
    }
}

In your test project’s module build.gradle.kts:

dependencies {
    implementation("com.yourdomain:my-awesome-library:0.0.1")
}

Sync or build your project. Gradle will fetch the library from your local Maven repository. You’ve effectively integrated your freshly built library into the consumer project without any external publishing steps.

Step 3: Iteration Workflow

As you develop new features or fix issues in your library, simply repeat:

  1. Modify library code
  2. Run ./gradlew publishToMavenLocal in your library project
  3. Refresh or rebuild your test project

Your test project will now consume the updated version from mavenLocal().

This rapid iteration cycle means you get immediate feedback on whether your changes integrate smoothly with the consumer.

Composite Builds for Even Faster Iteration

While publishing to Maven local is convenient, it can become tedious if you’re making frequent changes. Composite builds allow you to treat your test project and library as a single workspace:

In your test project’s settings.gradle.kts:

includeBuild("../path-to-my-awesome-library")

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Now, your test project will directly use the library’s source. Any changes made to the library are instantly available. No need to re-run publishToMavenLocal(). This approach is especially useful when working on KMM libraries, as it provides instant updates to both Android and iOS code in your consumer project.

Troubleshooting Tips
  • Artifacts Not Found: Double-check that groupIdartifactId, and version in the consumer project match exactly what you set in the library. Ensure mavenLocal() is present before other repositories.
  • Re-Sync After Changes: If you’ve re-published the library, refresh the consumer project so Gradle picks up the changes.
  • Gradle or Kotlin Version Compatibility: Make sure you’re using compatible versions. For KMM libraries, ensure both library and consumer projects use supported Kotlin and Gradle plugin versions. If you’re using newer features like applyDefaultHierarchyTemplate(), ensure it’s available in your Kotlin version.
  • Check for a .module File: The .module file helps Gradle resolve platform-specific variants, especially for KMM libraries.
When to Use Each Approach
  • Local Publishing (mavenLocal): Perfect for quick validation without a full release. Ideal when you’re mostly finished with a feature and want to test integration.
  • Composite Builds: Great when constantly tweaking the library. Changes are instantly reflected in your test project, speeding up the development loop.
Conclusion

Testing your Android or KMM library locally is a powerful technique to streamline development. By publishing to Maven Local or using composite builds, you shorten the feedback loop, enabling faster development, quicker validation, and ultimately delivering more robust libraries to your consumers.

This article was previously published on proandroiddev.com.

Menu