A vendor sends you one opaque .aar file and says, “drop this into the app.” That sounds simple until Gradle sync succeeds but the release build loses a class, a native library is missing on one device, or R8 removes the SDK entry point. The dependable approach is to treat the AAR as a binary dependency with a known contract—not as a mysterious Android Studio module.
AAR, JAR, or Maven coordinate?
An AAR is a ZIP-based Android library archive. It can contain compiled classes, resources, a manifest, assets, native libraries, lint checks, Prefab packages, and consumer shrinker rules.
A JAR contains JVM classes and resources but does not provide Android resources or an Android manifest.
A Maven coordinate such as
com.vendor:sdk:2.4.1points to an artifact plus metadata, which lets Gradle resolve transitive dependencies and versions.A local AAR is reasonable for a proprietary SDK, an offline build, or a short evaluation. It becomes harder to govern as teams and versions multiply.
1. Put the binary inside the consuming module
mkdir -p app/libs
cp /path/to/vendor-sdk-2.4.1.aar app/libs/The app module now owns a versioned copy at app/libs/vendor-sdk-2.4.1.aar.Why the location and filename matter
app/libsis a convention, not a Gradle magic directory; the dependency declaration below is what makes the file participate in the build.A versioned filename makes reviews, cache diagnosis, and rollback much less ambiguous than
sdk.aar.mkdir -pis safe and idempotent;cpreplaces an existing file with the same destination name, so compare checksums before upgrading.Confirm that your license permits committing the binary. Private artifact storage is often preferable to Git for commercial SDKs.
2. Declare the AAR in Kotlin DSL
dependencies {
implementation(files("libs/vendor-sdk-2.4.1.aar"))
}What this one line actually does
files(...)creates a file dependency relative to the module directory.implementationplaces the AAR on the app compile and runtime classpaths without exposing it as a public API dependency.Android Gradle Plugin merges the AAR manifest and resources while packaging its bytecode and compatible native libraries into the app.
The declaration does not discover libraries that the AAR itself was compiled against; add those separately from vendor documentation.
Groovy DSL equivalent
dependencies {
implementation files('libs/vendor-sdk-2.4.1.aar')
}Same dependency, different build-script language
Use the syntax matching the file already in the module:
.gradle.ktsis Kotlin DSL and.gradleis Groovy DSL.Do not paste both variants into one project.
Keep the declaration in the module that consumes the SDK, normally
:app, rather than the root build file.After editing, run a Gradle sync or a wrapper build so the declaration is evaluated.
When a file tree is appropriate
dependencies {
implementation(
fileTree(mapOf(
"dir" to "libs",
"include" to listOf("*.jar", "*.aar"),
)),
)
}Convenient, but less explicit
The patterns select every matching JAR and AAR in
app/libs; the internal asterisk in*.aaris a glob character, not emphasis markup.A file tree is useful for a controlled bundle of related binaries but hides the exact dependency list from code review.
Prefer individual
files(...)declarations when each binary has its own version, license, owner, or transitive requirements.Do not combine an individual declaration and a matching file tree or the artifact may be added twice.
Prefer repository metadata for a real SDK
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven(url = uri("https://packages.vendor.example/android"))
}
}Repository boundaries belong in settings
Replace the example URL with the vendor’s documented HTTPS repository; it is intentionally not a working endpoint.
dependencyResolutionManagementcentralizes repositories and works with projects that restrict module-level repository declarations.Use repository credentials from Gradle properties, environment-backed credentials, or a secret manager—never commit tokens in this file.
Restrict repository content to the vendor group when possible to reduce accidental or malicious dependency substitution.
dependencies {
implementation("com.vendor:sdk:2.4.1")
}Why coordinates scale better
The three fields are group, artifact, and version.
Gradle reads repository metadata to resolve declared transitive dependencies and reconcile repeated modules.
A fixed version is reproducible; avoid dynamic selectors such as
2.+in production builds.Use dependency locking or verification when your supply-chain policy requires stronger reproducibility.
The trap: an AAR does not bundle every dependency
A loose AAR file gives Gradle the archive, not the POM that normally describes its dependency graph. If the SDK uses OkHttp, Kotlin coroutines, Play services, or another vendor library without embedding it, your app must declare the compatible dependency explicitly. Ask the producer for a dependency list, supported AGP/Kotlin versions, minimum SDK, required permissions, consumer rules, and supported ABIs.
dependencies {
implementation(files("libs/vendor-sdk-2.4.1.aar"))
// Add only dependencies required by the vendor documentation.
implementation("com.example:vendor-runtime-dependency:1.7.0")
}Never guess the missing coordinate
The second coordinate is a placeholder that demonstrates placement; it must be replaced or removed.
A compile-time
Unresolved referencecan mean the class is absent from the AAR or its dependency is missing.A runtime
NoClassDefFoundErroroften means compilation saw an API that packaging or runtime resolution did not supply.Align versions with the producer’s tested matrix instead of selecting the newest release blindly.
Inspect the archive before trusting it
unzip -l app/libs/vendor-sdk-2.4.1.aar
sha256sum app/libs/vendor-sdk-2.4.1.aarExpect AndroidManifest.xml and, depending on the SDK, classes.jar, res/, assets/, jni/, proguard.txt, lint.jar, api.jar, or prefab/ entries, followed by a SHA-256 digest.The archive tells you what can enter the app
unzip -llists names without extracting or executing the library.classes.jarcarries JVM bytecode;res/and the manifest enter Android merge steps.jni/<abi>/*.soreveals which CPU architectures have native binaries.proguard.txtor consumer rules can affect app shrinking; review them as third-party build input.Compare the SHA-256 value with a checksum delivered through a trusted vendor channel and record it with the upgrade review.
App consumer versus library producer
implementationin a library controls API exposure but does not magically copy an arbitrary local AAR into the generated AAR.Publishing with Maven metadata lets consumers retrieve both artifacts as a dependency graph.
apiexposes dependency types to consumers and should be used only when those types are part of your public API.Wrapping or merging third-party binaries may violate licensing and can create duplicate resources/classes; obtain explicit permission and test the result.
Build and inspect the resolved graph
./gradlew :app:assembleDebug :app:lintDebug
./gradlew :app:dependencies --configuration debugRuntimeClasspathThe build should complete, lint should report no new SDK integration errors, and the runtime classpath report should contain expected repository dependencies. File dependencies can appear without rich transitive metadata.A sync is not a release test
Use the checked-in Gradle wrapper so local and CI builds use the project’s configured Gradle version.
assembleDebugexercises compilation, manifest/resource merge, dexing, and packaging for that variant.lintDebugcatches manifest, API-level, resource, and integration issues that compilation can miss.The dependency report explains selected versions but cannot invent dependency metadata for a loose AAR.
Repeat with the actual release variant because shrinking, signing, build constants, and resources may differ.
Release, R8, resources, and native code
Run a minified release build and exercise SDK entry points reached through reflection, JNI, serialization, or manifest registration.
Prefer consumer rules shipped by the library producer. Add app keep rules only for behavior you can explain and test.
Inspect manifest-merger reports for exported components, permissions, authorities, features, and conflicting attributes.
Prefix library resources where possible; app resources and multiple AARs share merge namespaces and can collide.
For native SDKs, test every shipped ABI and device architecture. An AAR containing only
arm64-v8acannot serve anx86_64emulator process.Check
minSdk, compile requirements, Kotlin/JVM bytecode target, namespace behavior, and AAR metadata compatibility supplied by the producer.
Common failures, translated
Could not find ...aar: the path is wrong relative to the consuming module, filename case differs, or the file is absent in CI.Duplicate class: the AAR embeds a class also supplied by another JAR/AAR, or the same binary is declared twice.NoClassDefFoundError: a runtime dependency is missing, excluded, incompatible, or stripped from the final variant.Manifest merger failed: the library declares a conflicting component, provider authority, SDK constraint, or manifest attribute.Android resource linking failed: resources conflict, an expected dependency resource is missing, or the binary is incompatible with the build setup.UnsatisfiedLinkError: the native library is absent for the running ABI, a dependent shared object is missing, or JNI names/signatures do not match.Direct local .aar file dependencies are not supported when building an AAR: publish dependencies with metadata or restructure modules instead of expecting nested binary fusion.Class works in debug but not release: inspect mapping/usage output and vendor consumer rules; reflection or JNI probably needs targeted preservation.
A practical acceptance checklist
The artifact came from an authenticated source, its checksum and version are recorded, and its license is approved.
The dependency is declared once in the correct module and builds on a clean checkout in CI.
Vendor transitive dependencies are explicit or arrive through trusted repository metadata.
Debug and minified release variants pass compile, lint, install, startup, and feature tests.
Manifest components, permissions, resources, consumer rules, native ABIs, minimum SDK, and privacy/data behavior have been reviewed.
The team has an owner, upgrade procedure, rollback version, vulnerability-monitoring path, and source for release notes.
Primary references
Create an Android library documents direct AAR/JAR dependencies, Maven publication, transitive metadata, consumer rules, resource merging, and AAR anatomy.
Add build dependencies explains Gradle configurations and Android dependency management.
Gradle repository types explains why flat directory repositories are discouraged.
Prepare an Android library for release covers AAR metadata and compatibility requirements.
Comments and corrections