Note - 2016-10-22 - I missed Gradle Composite Builds which do something very similar

I often find myself on a project with multiple applications depending on common libraries, so I tend to end up with a super project looking like this:


All three projects are separate git projects, separately built and deployed; the apps are on a continuous deployment pipeline, the lib requires a decision to cut a release to move it from a SNAPSHOT version to a fixed version. The top level project is just a convenience to allow checking them all out in one shot and building them all with one command.

During development of a feature that requires a change to the lib, I would update the dependency in the app that needs the feature to X.X.X-SNAPSHOT and work on them both at the same time.

In Maven this worked OK for development - both Maven and most IDEs would successfully resolve any SNAPSHOT dependencies locally if possible. Then after cutting a release of the app you only had to delete the -SNAPSHOT bit from the dependency version and job done.

However, Gradle does not do this by default; you have to specify the dependency as being part of your multi-module build as so:

dependencies {
  compile project(':lib')

This is much more invasive - changing to a release version of the lib now requires replacing that with:

dependencies {
  compile 'mygroup:lib:1.2.3'

So you have to add the group of the lib, and know which precise version to specify, rather than just deleting -SNAPSHOT from the version. This makes it harder to automate changing the dependency - ideally, I would like to release the lib automatically as part of the continuous deployment process of the app after pushing a commit of the app which references a SNAPSHOT version of the lib.

I’m experimenting with a way around this by manipulating the top level gradle build as so:

subprojects.each { it.evaluate() }

def allDependencies = subprojects.collect { it.configurations.collect { it.dependencies }.flatten() }.flatten()
def localDependencies = allDependencies.findAll { dependency ->
    subprojects.any { == && it.version == dependency.version && == }

subprojects {
    configurations.all {
        resolutionStrategy.dependencySubstitution {
            localDependencies.each {
                substitute module("${}:${}:${it.version}") with project(":${}")

This effectively gives the Maven behaviour - and IntelliJ at least respects it correctly and resolves the dependencies to the same workspace

You can play with an example here: