Skip to content

Using Shadow in Multi-Project Builds

When using Shadow in a multi-project build, project dependencies will be treated the same as external dependencies. That is a project dependency will be merged into the ShadowJar output of the project that is applying the Shadow plugin.

Depending on the Shadow Jar from Another Project

In a multi-project build there may be one project that applies Shadow and another that requires the shadowed JAR as a dependency. In this case, use Gradle’s normal dependency declaration mechanism to depend on the shadow configuration of the shadowed project.

dependencies {
  implementation(project(path = ":api", configuration = "shadow"))
}
dependencies {
  implementation project(path: ':api', configuration: 'shadow')
}

Making the Shadowed JAR the Default Artifact

When a project needs to expose the shadowed JAR as its default output — so that consumers can depend on it without specifying the shadow configuration explicitly — you can reconfigure the consumable configurations apiElements and runtimeElements to publish the shadowed JAR instead of the regular JAR.

As a reminder, configurations like api and implementation are where dependencies are declared (declarable), while apiElements and runtimeElements are what Gradle consumes when projects depend on each other.

By tuning these consumable configurations, a simple declaration like implementation(project(":api")) will resolve to the shadowed JAR by default, preventing accidental consumption of the unshadowed artifact.

In the shadowed project (:api):

plugins {
  `java-library`
  id("com.gradleup.shadow")
}

configurations {
  named("apiElements") {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.shadowJar)
  }
  named("runtimeElements") {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.shadowJar)
  }
}
plugins {
  id 'java-library'
  id 'com.gradleup.shadow'
}

configurations {
  apiElements {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.named('shadowJar'))
  }
  runtimeElements {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.named('shadowJar'))
  }
}

Important

Clearing outgoing.variants ensures Gradle doesn’t select the unshadowed classes variant by default during compilation.

Consuming projects can then depend on :api without specifying the shadow configuration:

dependencies {
  implementation(project(":api"))
}
dependencies {
  implementation project(':api')
}

Excluding Transitive Dependencies

If you want to exclude transitive dependencies that were bundled into the shadow JAR, you can add exclude rules to the configurations as well:

configurations {
  named("apiElements") {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.shadowJar)
    exclude(group = "com.example", module = "bundled-library")
  }
  named("runtimeElements") {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.shadowJar)
    exclude(group = "com.example", module = "bundled-library")
  }
}
configurations {
  apiElements {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.named('shadowJar'))
    exclude group: 'com.example', module: 'bundled-library'
  }
  runtimeElements {
    outgoing.artifacts.clear()
    outgoing.variants.clear()
    outgoing.artifact(tasks.named('shadowJar'))
    exclude group: 'com.example', module: 'bundled-library'
  }
}