Blog Infos
Author
Published
Topics
, , , ,
Published

Photo by Will Porada on Unsplash

Inside your repo, you want deep modularization — :core:ui:network:analytics, internal utilities, maybe even per-feature modules. But the moment you publish, that beautiful graph turns into a UX problem for your consumers:

  • They’re forced to add three, five, ten Gradle coordinates just to use “one” SDK.
  • Version mismatches across sub-artifacts turn into subtle bugs.
  • Every breaking change becomes a negotiation: “Which of your artifacts did you update?”

For years, the answer was some variation of a “fat AAR” hack: unzip, merge, repack, hope manifest and resources don’t explode.

With Android Gradle Plugin 8.12+, Google finally ships the official alternative:

The Fused Library plugin (com.android.fused-library) lets you bundle multiple Android library modules into a single publishable AAR, while keeping your internal project fully modular.

In this article, I’ll walk through what the Fused Library plugin actually does, how I wired it into a real multi-module SDK, and where the sharp edges and surprises showed up in practice.

1. What the Fused Library plugin actually does

At a high level, the Fused Library plugin is a pure build-time plugin:

  • It creates a special module (a fused library module).
  • That module has no sources of its own by default.
  • Instead, it packages other Android libraries and external dependencies into one AAR.

You still develop like this:

:orbit-core
:orbit-components
:orbit-utils
:some-third-party-wrapper

but you publish something like:

com.example.orbit:orbit-sdk:1.0.0

Under the hood, the plugin:

  • Merges Android manifests.
  • Merges Android resources (with deterministic conflict rules).
  • Aggregates compiled classes and Java resources.
  • Produces a single AAR that your users consume as a normal Android library.

Status:

  • The plugin is Preview, introduced in AGP 8.12 and available in AGP 8.12+.
  • It’s designed purely for Android libraries, not apps.
2. When you should (and shouldn’t) use it

The Fused Library plugin makes sense when:

  • You ship a public SDK / UI Kit / internal platform library.
  • Your internal project is highly modular, but the public surface should be one dependency.
  • You want better control over which pieces of your internal structure leak into consumers’ build graphs.

It’s less useful when:

  • You only have one or two modules — a regular com.android.library is fine.
  • You rely heavily on data binding in the modules you want to fuse (not supported today).
  • You need to publish multiple build types or product flavors via a single artifact (not supported; you create separate fused libraries per variant).
3. Prerequisites and feature flags

Before touching Gradle files, there are a few non-negotiables:

  1. Android Gradle Plugin version
  • You must use AGP 8.12 or higher.

2. Enable fused library support globally

In your root gradle.properties:

android.experimental.fusedLibrarySupport=true

(Optional, but important to understand) Publication-only restriction
By default, a fused library module is meant for publication only. You usually don’t depend on :fused directly from an app module; you depend on the published AAR.

If you really need to consume the fused module as a project dependency (e.g., during migration), you can override:

android.experimental.fusedLibrarySupport.publicationOnly=false

Just keep in mind that Studio support for that scenario is still experimental.

4. Creating a fused library module

Let’s say you’re building a UI toolkit called Orbit.

Internally, you have:

:orbit-core
:orbit-components
:orbit-utils

You want to ship a single artifact: com.example.orbit:orbit-sdk:1.0.0

4.1. Wire up the module in settings and version catalog

  1. In settings.gradle.kts:
include(":orbit-sdk")

2. In gradle/libs.versions.toml, under [plugins]:

android-fusedlibrary = { id = "com.android.fused-library", version.ref = "agp" }

3. In the top-level build.gradle.kts:

plugins {
    alias(libs.plugins.android.fusedlibrary) apply false
    // other plugins...
}

These steps match the official docs and make the fused plugin available to your modules.

4.2. Minimal orbit-sdk/build.gradle.kts

Now, create the module directory orbit-sdk/ and add build.gradle.kts:

plugins {
    alias(libs.plugins.android.fusedlibrary)
    `maven-publish`
}

androidFusedLibrary {
    namespace = "com.example.orbit.sdk"
    minSdk = 21
}

dependencies {
    // will fill this in the next section
}

Key differences from a “normal” Android library:

  • You use androidFusedLibrary {}, not android {}.
  • This module is about aggregation, not about your own sources.
5. Bundling modules and dependencies with include

The heart of the plugin is the include configuration.

  • Anything you declare via include(...) gets physically packaged into the fused AAR.
  • Transitive dependencies are not automatically fused; you must declare anything you want inside the AAR explicitly with include.

For Orbit, you might do:

dependencies {
    // Internal modules to be merged into the final AAR
    include(project(":orbit-core"))
    include(project(":orbit-components"))
    include(project(":orbit-utils"))

    // External dependencies you want to ship *inside* the AAR
    // (e.g., shading a font pack or a very specific helper library)
    include("mycoolfonts:font-wingdings:5.0")

    // Dependencies your consumers must see and manage explicitly
    // remain as regular implementation/api deps (end up in the POM)
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
}

A couple of important nuances:

  • If :orbit-core depends on :logginginclude(project(":orbit-core")) does not automatically fuse :logging.
    If you want :logging inside the AAR, you must also add include(project(":logging")).
  • For local Android library projects referenced via include, you’ll typically publish them to a local Maven repo along with the fused AAR when testing end-to-end.
6. Publishing the fused AAR correctly

Publishing a fused library is very similar to publishing a regular Android library, but with one important detail:

Your MavenPublication must inherit from the fusedLibraryComponent.

This ensures the right artifact and metadata are used.

6.1. Basic Maven Publish configuration

plugins {
    alias(libs.plugins.android.fusedlibrary)
    `maven-publish`
}

androidFusedLibrary {
    namespace = "com.example.orbit.sdk"
    minSdk = 21
}

dependencies {
    include(project(":orbit-core"))
    include(project(":orbit-components"))
    include(project(":orbit-utils"))
}

publishing {
    publications {
        register<MavenPublication>("release") {
            groupId = "com.example.orbit"
            artifactId = "orbit-sdk"
            version = "1.0.0"

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

That’s the officially documented pattern: fusedLibraryComponent is the component that knows about the merged contents of your fused library.

6.2. Local testing via a file-based Maven repo

Before shipping to Maven Central or an internal Artifactory, you should test the fused library by consuming it like any other remote dependency.

The docs suggest a simple local Maven repo in your fused module:

plugins {
    alias(libs.plugins.android.fusedlibrary)
    `maven-publish`
}

repositories {
    maven {
        name = "orbitLocalRepo"
        url = uri(layout.buildDirectory.dir("orbitLocalRepo"))
    }
}

Then, in your app or sample module:

repositories {
    maven {
        url = uri("${rootProject.projectDir}/orbit-sdk/build/orbitLocalRepo")
    }
    mavenCentral()
}

dependencies {
    implementation("com.example.orbit:orbit-sdk:1.0.0")
}

This simulates real-world consumption without publishing outside your machine or CI environment.

7. How the plugin fuses artifacts (and where it fails)

The “Behavior and safeguards” section of the docs is where the interesting details live.

7.1. Classes

  • If two included libraries contribute classes to the same classpath, the build fails.
  • There is no silent override for duplicate classes.

This is good: if your internal modules accidentally bundle competing implementations, you find out at build time, not in a crash report.

7.2. Android resources

  • Android resources are merged much like in a normal multi-module app.
  • When two libraries define a resource with the same name, the order of dependencies in include {} determines which one wins.

Practically, this means you should:

  • Adopt consistent resource naming (e.g., prefixing with orbit_).
  • Be deliberate about including order if you want overrides.

7.3. AAR metadata

  • AAR metadata values (such as minCompileSdk) are merged by taking the highest value from all dependencies.
  • You can override these values explicitly:
androidFusedLibrary {
    aarMetadata {
        minCompileSdk = 34
        minCompileSdkExtension = 1
    }
}

7.4. Java resources

  • Java resources (files in src/main/resources etc.) cannot share the same path across included libraries.
  • Duplicate paths → build failure.

If you rely on Java resources (e.g., service loaders), make sure they are centralized or generated in a single module.

8. Known limitations you must design around

The plugin is still in Preview, and Google is explicit about current limitations:

  • No sources JAR

As of now, fused libraries cannot generate source JARs.

If you rely on source JARs for your SDK (e.g., for IDE navigation in consumer projects), you’ll need a workaround or wait for future AGP support.

  • File dependencies on .aar are not supported

You can’t include(files("some.aar")) and have it fused; you need proper Maven coordinates or project modules.

  • No RenderScript / Prefab support

Libraries using RenderScript or Prefab artifacts can’t currently be fused.

  • No data binding support

Fusing libraries that use data binding is not supported. View Binding is fine; classic Data Binding is not.

  • No multiple variants in a single fused library

You can’t fuse multiple build types / flavors into one module and expect them all to work.

Instead, create separate fused library modules (or separate publications) per variant as needed.

  • Publication-only by default

A fused library module is intended for publication and external consumption.

Direct project dependency consumption is experimental and must be explicitly enabled via
android.experimental.fusedLibrarySupport.publicationOnly=false.

If your current SDK heavily uses data binding, multiple flavors, or custom AAR file dependencies, you might need to refactor before adopting the plugin.

9. Understanding what ended up where

Because a fused library has no sources of its own and is “just” a bundle of other libraries, it’s easy to lose track of which dependency is inside the AAR and which is still external.

The plugin exposes a helpful report:

Run:

./gradlew :orbit-sdk:report

It produces a JSON report in orbit-sdk/build/reports, listing:

Which dependencies have been merged into the fused AAR.

Which dependencies remain needed to build the AAR (external deps).

You can also use ./gradlew :orbit-sdk:dependencies to inspect the plugin’s internal configurations when debugging dependency issues.

10. Putting it all together for a real SDK

For a serious Android SDK, a realistic structure might look like this:

:core:logging
:core:network
:core:design-system
:feature:payments
:feature:auth
:feature:analytics

:sdk-public-api      // thin facade library
:example-app         // sample app
:payments-fused      // fused library for payments-only distribution
:full-sdk-fused      // fused library for full SDK distribution

You can then:

  • Keep :sdk-public-api as a thin facade dependency, your users compile against.
  • Use :full-sdk-fused to bundle everything you consider “mainline SDK”.
  • Create additional fused artifacts (:payments-fused:lite-fused) for alternative distributions.

Each fused module:

  • Is trivial to wire with androidFusedLibrary {} and include(...).
  • Publishes exactly one coordinate for a given audience.
  • Keeps your internal architecture private and flexible.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Conclusion

The Fused Library plugin is the missing official piece for Android SDK authors:

  • It gives you one AAR on the outside, with many modules on the inside.
  • It’s part of AGP (no more fragile third-party fat-AAR scripts).
  • It defines clear, documented rules for merging classes, resources, and metadata, and fails loudly when it can’t decide.

There are still rough edges — no source JARs yet, no Data Binding, no multi-variant fusing — but the direction is clear: if you maintain a serious Android SDK or internal platform library, it’s worth prototyping a fused setup now and tracking the plugin’s evolution alongside AGP 8.12+ and 9.0. The Fused Library plugin is the first official tool that lets you honor both sides of that equation.

This article was previously published on proandroiddev.com

Menu