Gradle
Build.gradle
Java
.jar file
Dependency Management

How to add local .jar file dependency to build.gradle file?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

To add a local .jar file as a dependency in Gradle, use implementation files('libs/example.jar') in your dependencies block. For multiple jars in a directory, use implementation fileTree(dir: 'libs', include: ['*.jar']). These are the two most common approaches, and each has trade-offs around scalability, version tracking, and build reproducibility that are worth understanding before choosing one.

Why You Might Need Local Jars

Most Java dependencies come from Maven Central or other remote repositories, but there are legitimate cases where a local jar is necessary:

  • Proprietary libraries that are not published to any repository
  • Modified forks of open-source libraries with custom patches
  • Legacy projects where migrating to a repository manager is not yet feasible
  • Offline builds in air-gapped environments without internet access
  • Vendor SDKs distributed as downloadable jar files

Project Structure Convention

Place local jars in a libs directory at the root of your project or module. This is a widely recognized convention that other developers will understand immediately.

 
1my-project/
2  libs/
3    payment-sdk-2.1.0.jar
4    internal-utils-1.0.jar
5  src/
6    main/
7      java/
8  build.gradle

For multi-module projects, each module can have its own libs directory, or you can share one at the root level.

Method 1: Direct File Reference

The simplest approach references specific jar files directly.

groovy
1dependencies {
2    implementation files('libs/payment-sdk-2.1.0.jar')
3    implementation files('libs/internal-utils-1.0.jar')
4}

In Kotlin DSL (build.gradle.kts):

kotlin
1dependencies {
2    implementation(files("libs/payment-sdk-2.1.0.jar"))
3    implementation(files("libs/internal-utils-1.0.jar"))
4}

This is explicit and predictable. You know exactly which jars are included. The downside is that adding a new jar requires editing the build file.

Method 2: File Tree (Directory Scan)

To include all jars in a directory without listing each one individually:

groovy
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

In Kotlin DSL:

kotlin
dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}

This scales better when you have many jars, but it sacrifices visibility. A new jar dropped into the libs directory is automatically included without any explicit declaration, which can introduce surprises.

You can also filter by pattern:

groovy
1dependencies {
2    // Include only specific jars
3    implementation fileTree(dir: 'libs', include: ['payment-*.jar', 'internal-*.jar'])
4
5    // Exclude certain jars
6    implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['*-test.jar'])
7}

Method 3: Flat Directory Repository

Gradle supports declaring a local directory as a flat repository. This approach allows you to reference jars by name without the .jar extension.

groovy
1repositories {
2    flatDir {
3        dirs 'libs'
4    }
5}
6
7dependencies {
8    implementation name: 'payment-sdk-2.1.0'
9}

The flatDir repository tells Gradle to look in libs/ for artifacts. Gradle infers the .jar extension automatically.

This method integrates more naturally with Gradle's dependency resolution, but it has a significant limitation: flat directory repositories do not support transitive dependencies or metadata (POM files). Gradle cannot resolve the jar's own dependencies.

For teams that need reproducible builds and proper version management, publishing the jar to a local Maven repository is the cleanest solution.

Install the jar into the local Maven cache

bash
1mvn install:install-file \
2  -Dfile=libs/payment-sdk-2.1.0.jar \
3  -DgroupId=com.example \
4  -DartifactId=payment-sdk \
5  -Dversion=2.1.0 \
6  -Dpackaging=jar

Reference it in build.gradle

groovy
1repositories {
2    mavenLocal()
3    mavenCentral()
4}
5
6dependencies {
7    implementation 'com.example:payment-sdk:2.1.0'
8}

This gives you proper versioning, works with Gradle's dependency resolution, and integrates with existing repository infrastructure. The jar is stored in ~/.m2/repository/ like any other Maven artifact.

Comparison of Methods

MethodVersioningTransitive DepsTeam FriendlySetup Effort
files()ManualNoLowMinimal
fileTree()ManualNoLowMinimal
flatDirManualNoMediumLow
mavenLocal()ProperYes (with POM)HighModerate
Private repository (Nexus/Artifactory)ProperYesHighHigh

Multi-Module Projects

In multi-module Gradle projects, the path in files() or fileTree() is relative to the module's directory, not the root project.

groovy
1// In app/build.gradle, this looks in app/libs/
2dependencies {
3    implementation files('libs/payment-sdk-2.1.0.jar')
4}

To reference jars from the root project's libs directory:

groovy
1// In app/build.gradle, referencing root project libs
2dependencies {
3    implementation files("${rootProject.projectDir}/libs/payment-sdk-2.1.0.jar")
4}

For sharing a common set of jars across modules, define them in the root build.gradle using subprojects or allprojects:

groovy
1// In root build.gradle
2subprojects {
3    dependencies {
4        implementation fileTree(dir: "${rootProject.projectDir}/libs", include: ['*.jar'])
5    }
6}

Android Projects

Android projects follow the same patterns, but Android Gradle Plugin versions before 3.0 used compile instead of implementation. Modern Android projects should use implementation or api.

groovy
1// Android module build.gradle
2android {
3    // ...
4}
5
6dependencies {
7    implementation fileTree(dir: 'libs', include: ['*.jar'])
8    // For AAR files
9    implementation fileTree(dir: 'libs', include: ['*.aar'])
10}

Android Studio generates this fileTree declaration by default in new projects, which is why many Android developers encounter local jar dependencies early.

Common Pitfalls

  • Forgetting to commit the jar to version control. If the jar is in .gitignore or simply not committed, the build breaks for other team members. Either commit the jars or use a shared repository.
  • Version conflicts with remote dependencies. A local jar may bundle classes that overlap with a remote dependency, causing ClassNotFoundException or NoSuchMethodError at runtime. Use ./gradlew dependencies to inspect the full dependency tree.
  • Using compile instead of implementation. The compile configuration is deprecated since Gradle 3.0. Use implementation (private to the module) or api (exposed to consumers).
  • Relative path confusion in multi-module builds. files('libs/foo.jar') resolves relative to the current module, not the root project. Use ${rootProject.projectDir} for root-relative paths.
  • No transitive dependency resolution. Local jars do not carry POM metadata. If the jar depends on other libraries, you must declare those dependencies manually or use mavenLocal() with a proper POM.
  • Stale jars after updates. Replacing a jar in the libs directory does not always trigger a rebuild. Run ./gradlew clean after replacing a local jar to ensure the build picks up the new version.

Summary

  • Use files('libs/example.jar') for a small number of explicitly named jars.
  • Use fileTree(dir: 'libs', include: ['*.jar']) to include all jars in a directory automatically.
  • Use flatDir when you want repository-style name-based references without the jar extension.
  • For team environments, publish jars to mavenLocal() or a private repository (Nexus, Artifactory) for proper versioning and transitive dependency support.
  • In multi-module projects, paths in files() are relative to the module directory. Use ${rootProject.projectDir} to reference jars from the root.
  • Always verify the dependency tree with ./gradlew dependencies to catch version conflicts between local and remote jars.

Course illustration
Course illustration

All Rights Reserved.