
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 defineMavenPublication
objects within apublishing { publications { ... } }
block, specifying their coordinates and contents (often derived from Gradle components likecomponents["release"]
for Android or using the defaultkotlinMultiplatform
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 (likekotlinMultiplatform
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 dedicatedmavenPublishing { ... }
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 likesignAllPublications()
andpublishToMavenCentral(...)
to the samemavenPublishing
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 group
, version
, 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 fromcomponents["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:
- Modify library code
- Run
./gradlew publishToMavenLocal in your library project
- 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
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
groupId
,artifactId
, andversion
in the consumer project match exactly what you set in the library. EnsuremavenLocal()
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.