Chapter 28. Dependency Management

28.1. Introduction

This chapter gives an overview of issues related with dependency management and presents how Gradle can be used to overcome them.

Gradle offers a very good support for dependency management. If you are familiar with Maven or Ivy approach you will be delighted to learn that:

  • All the concepts that you already know and like are still there and are fully supported by Gradle. The current dependency management solutions all require to work with XML descriptor files and are usually based on remote repositories for downloading the dependencies. Gradle fully supports this approach.

  • Gradle works perfectly with your existent dependency management infrastructure, be it Maven or Ivy. All the repositories you have set up with your custom pom or ivy files can be used as they are. No changes necessary.

  • Additionally, Gradle offers a simpler approach, which might be better suited for some projects.

28.2. Dependency management overview

We think dependency management is very important for almost any project. Yet the kind of dependency management you need depends on the complexity and the environment of your project. Is your project a distribution or a library? Is it part of an enterprise environment, where it is integrated into other projects builds or not? But all types of projects share the following requirements:

  • The version of the jar must be easy to recognize. Sometimes the version is in the Manifest file of the jar, often not. And even if, it is rather painful to always look into the Manifest file to learn about the version. Therefore we think that you should only use jars which have their version as part of their file name.

  • It hopes to be clear what are the first level dependencies and what are the transitive ones. There are different ways to achieve this. We will look at this later.

  • Conflicting versions of the same jar should be detected and either resolved or cause an exception.

28.2.1. Versioning the jar name

Why do we think this is necessary? Without a dependency management as described above, your are likely to burn your fingers sooner or later. If it is unclear which version of a jar your are using, this can introduce subtle bugs which are very hard to find. For example there might be a project which uses Hibernate 3.0.4. There are some problems with Hibernate so a developer installs version 3.0.5 of Hibernate on her machine. This did not solve the problem but she forgot to roll back Hibernate to 3.0.4. Weeks later there is an exception on the integration machine which can't be reproduced on the developer machine. Without a version in the jar name this problem might take a long time to debug. Version in the jar names increases the expressiveness of your project and makes it easier to maintain.

28.2.2. Transitive dependency management

Why is transitive dependency management so important? If you don't know which dependencies are first level dependencies and which ones are transitive you will soon lose control over your build. Even Gradle has already 20+ jars. An enterprise project using Spring, Hibernate, etc. easily ends up with 100+ jars. There is no way to memorize where all these jars come from. If you want to get rid of a first level dependency you can't be sure which other jars you should remove. Because a dependency of a first level dependency might also be a first level dependency itself. Or it might be a transitive dependency of another of your first level dependencies. Many first level dependencies are runtime dependencies and the transitive dependencies are of course all runtime dependencies. So the compiler won't help you much here. The end of the story is, as we have seen very often, no one dares to remove any jar any longer. The project classpath is a complete mess and if a classpath problem arises, hell on earth invites you for a ride. In one of my former projects, I found some ldap related jar in the classpath, whose sheer presence, as I found out after much research, accelerated LDAP access. So removing this jar would not have led to any errors at compile or runtime.

Gradle offers you different ways to express what are first level and what are transitive dependencies. Gradle allows you for example to store your jars in CVS or SVN without XML descriptor files and still use transitive dependency management. Gradle also validates your dependency hierarchy against the reality of your code by using only the first level dependencies for compiling.

28.2.3. Version conflicts

In your dependency description you tell Gradle which version of a dependency is needed by another dependency. This frequently leads to conflicts. Different dependencies rely on different versions of another dependency. The JVM unfortunately does not offer yet any easy way, to have different versions of the same jar in the classpath (see Section 28.2.4, “Dependency management and Java”). What Gradle offers you is a resolution strategy, by default the newest version is used. To deal with problems due to version conflicts, reports with dependency graphs are also very helpful. Such reports are another feature of dependency management.

28.2.4. Dependency management and Java

Traditionally, Java has offered no support at all for dealing with libraries and versions. There are no standard ways to say that foo-1.0.jar depends on a bar-2.0.jar. This has led to proprietary solutions. The most popular ones are Maven and Ivy. Maven is a complete build system whereas Ivy focuses solely on dependency management.

Both approaches rely on descriptor XML files, which contains information about the dependencies of a particular jar. Both also use repositories where the actual jars are placed together with their descriptor files. And both offer resolution for conflicting jar versions in one form or the other. Yet we think the differences of both approaches are significant in terms of flexibility and maintainability. Beside this, Ivy fully supports the Maven dependency handling. So with Ivy you have access to both worlds. We like Ivy very much. Gradle uses it under the hood for its dependency management. Ivy is most often used via Ant and XML descriptors. But it also has an API. We integrate deeply with Ivy via its API. This enables us to build new concepts on top of Ivy which Ivy does not offer itself.

Right now there is a lot of movement in the field of dependency handling. There is OSGi and there is JSR-294. [19] OSGi is available already, JSR-294 is supposed to be shipped with Java 7. These technologies deal, amongst many other things, also with a painful problem which is neither solved by Maven nor by Ivy. This is enabling different versions of the same jar to be used at runtime.

28.3. How to declare your dependencies

People who know Ivy have come across most of the concepts we are going to introduce now. But Gradle does not use any XML for declaring the dependencies (e.g. no ivy.xml file). It has its own notation which is part of the Gradle build file.

28.3.1. Configurations

Dependencies are grouped in configurations. Configurations have a name, a number of other properties, and they can extend each other. For examples see: Section 8.1, “Artifact configurations”. If you use the Java plugin, Gradle adds a number of pre-defined configurations to your build. The plugin also assigns configurations to tasks. See Section 18.4, “Dependency management” for details. Of course you can add your add custom configurations on top of that. There are many use cases for custom configurations. This is very handy for example for adding dependencies not needed for building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution). The configurations are managed by a configurations object. The closure you pass to the configurations object is applied against its API. To learn more about this API have a look at ConfigurationHandler .

28.3.2. Module dependencies

Module dependencies are the most common dependencies. They correspond to a dependency in an external repository.

Example 28.1. Module dependencies

build.gradle

dependencies {
    runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
    runtime 'org.springframework:spring-core:2.5', 'org.springframework:spring-aop:2.5'
    runtime(
        [group: 'org.springframework', name: 'spring-core', version: '2.5'],
        [group: 'org.springframework', name: 'spring-aop', version: '2.5']
    )
    runtime('org.hibernate:hibernate:3.0.5') {
        transitive = true
    }
    runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
    runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
        transitive = true
    }
}

Gradle provides different notations for module dependencies. There is a string notation and a map notation. A module dependency has an API which allows for further configuration. Have a look at ExternalModuleDependency to learn all about the API. This API provides properties and configuration methods. Via the string notation you can define a subset the properties. With the map notation you can define all properties. To have access to the complete API, either with the map or with the string notation, you can assign a single dependency to a configuration together with a closure.

If you declare a module dependency, Gradle looks for a corresponding module descriptor file (pom.xml or ivy.xml) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of this module (e.g. hibernate-3.0.5.jar) as well as its dependencies (e.g. cglib) are downloaded. If no such module descriptor file exists, Gradle looks for a file called hibernate-3.0.5.jar to retrieve. In Maven a module can only have one and only one artifact. In Gradle and Ivy a module can have multiple artifacts. Each artifact can have a different set of dependencies.

28.3.2.1. Artifact only notation

As said above, if no module descriptor file can be found, Gradle by default downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors, you want to download only the artifact jar, without the dependencies. [20] And sometimes you want to download a zip from a repository, that does not have module descriptors. Gradle provides an artifact only notation for those use cases - simply prefix the extension that you want to be downloaded with '@' sign:

Example 28.2. Artifact only notation

build.gradle

dependencies {
    runtime "org.groovy:groovy:1.5.6@jar"
    runtime group: 'org.groovy', name: 'groovy', version: '1.5.6', ext: 'jar'
}


An artifact only notation creates a module dependency which downloads only the artifact file with the specified extension. Existing module descriptors are ignored.

28.3.2.2. Classifiers

The Maven dependency management has the notion of classifiers. [21] Gradle supports this. To retrieve classified dependencies from a maven repository you can write:

Example 28.3. Dependency with classifier

build.gradle

compile "org.gradle.test.classifiers:service:1.0:jdk15@jar"
    otherConf group: 'org.gradle.test.classifiers', name: 'service', version: '1.0', classifier: 'jdk14'

As you can see in the example, classifiers can be used together with setting an explicit extension (artifact only notation).

28.3.3. Client module dependencies

Client module dependencies enable you to declare transitive dependencies directly in your build script. They are a replacement for a module descriptor XML file in an external repository.

Example 28.4. Client module dependencies - transitive dependencies

build.gradle

dependencies {
    runtime module("org.codehaus.groovy:groovy-all:1.5.6") {
        dependency("commons-cli:commons-cli:1.0") {
            transitive = false
        }
        module(group: 'org.apache.ant', name: 'ant', version: '1.7.0') {
            dependencies "org.apache.ant:ant-launcher:1.7.0@jar", "org.apache.ant:ant-junit:1.7.0"
        }
    }
}

This declares a dependency of your project on Groovy. Groovy itself has dependencies. But Gradle does not look for an XML descriptor to figure them out but gets the information from the build file. The dependencies of a client module can be normal module dependencies or artifact dependencies or another client module. Have also a look at the javadoc: ClientModule

In the current release client modules have one limitation. Let's say your project is a library and you want this library to be uploaded to your company's Maven or Ivy repository. Gradle uploads the jars of your project to the company repository together with the XML descriptor file of the dependencies. If you use client modules the dependency declaration in the XML descriptor file is not correct. We will improve this in a future release of Gradle.

28.3.4. Project dependencies

Gradle distinguishes between external dependencies and dependencies on projects which are part of the same multi-project build. For the latter you can declare Project Dependencies.

Example 28.5. Project dependencies

build.gradle

dependencies {
    compile project(':shared')
}

For more information see the javadoc for ProjectDependency

Multi-project builds are discussed in Chapter 31, Multi-project Builds.

Transitive compile dependencies are disabled by default. They can be enabled using:

configurations.compile.transitive = true

28.3.5. File dependencies

File dependencies allow you to directly add a set of files to a configuration, without first adding them to a repository. This can be useful if you cannot, or do not want to, place certain files in a repository. Or if you do not want to use any repositories at all for storing your dependencies.

To add some files as a dependency for a configuration, you simply pass a file collection as a dependency:

Example 28.6. File dependencies

build.gradle

dependencies {
    runtime files('libs/a.jar', 'libs/b.jar')
    runtime fileTree(dir: 'libs', includes: ['*.jar'])
}

File dependencies are not included in the published dependency descriptor for your project. However, file dependencies are included in transitive project dependencies within the same build. This means they cannot be used outside the current build, but they can be used with the same build.

You can declare which tasks produce the files for a file dependency. You might do this when, for example, the files are generated by the build.

Example 28.7. Generated file dependencies

build.gradle

dependencies {
    compile files("$buildDir/classes") {
        builtBy 'compile'
    }
}

task compile << {
    println 'compiling classes'
}

task list(dependsOn: configurations.compile) << {
    println "classpath = ${configurations.compile.collect {File file -> file.name}}"
}

Output of gradle -q list

> gradle -q list
compiling classes
classpath = [classes]

28.3.6. Excluding transitive dependencies

You can exclude a transitive dependency either by configuration or by dependency:

Example 28.8. Excluding transitive dependencies

build.gradle

configurations {
    compile.exclude module: 'commons'
    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
}

dependencies {
    compile("org.gradle.test.excludes:api:1.0") {
        exclude module: 'shared'
    }
}

If you define an exclude for a particular configuration, the excluded transitive dependency will be filtered for all dependencies when resolving this configuration or any inheriting configuration. If you want to exclude a transitive dependency from all your configurations you can use the Groovy spread-dot operator to express this in a concise way, as shown in the example. When defining an exclude, you can specify either only the organization or only the module name or both. Have also a look at the javadoc of Dependency and Configuration .

28.3.7. Optional attributes

All attributes for a dependency are optional, except the name. It depends on the repository type, which information is need for actually finding the dependencies in the repository. See Section 28.5, “Repositories”. If you work for example with Maven repositories, you need to define the group, name and version. If you work with filesystem repositories you might only need the name or the name and the version.

Example 28.9. Optional attributes of dependencies

build.gradle

dependencies {
    runtime ":junit:4.4", ":testng"
    runtime name: 'testng' 
}

You can also assign collections or arrays of dependency notations to a configuration:

Example 28.10. Collections and arrays of dependencies

build.gradle

List groovy = ["org.codehaus.groovy:groovy-all:1.5.4@jar",
               "commons-cli:commons-cli:1.0@jar",
               "org.apache.ant:ant:1.7.0@jar"]
List hibernate = ['org.hibernate:hibernate:3.0.5@jar', 'somegroup:someorg:1.0@jar']
dependencies {
    runtime groovy, hibernate
}

28.3.8. Dependency configurations

In Gradle a dependency can have different configurations (as your project can have different configurations). If you don't specify anything explicitly, Gradle uses the default configuration of the dependency. For dependencies from a Maven repository, the default configuration is the only available one anyway. If you work with Ivy repositories and want to declare a non-default configuration for your dependency you have to use the map notation and declare:

Example 28.11. Dependency configurations

build.gradle

dependencies {
    runtime group: 'org.somegroup', name: 'somedependency', version: '1.0', configuration: 'someConfiguration'
}

To do the same for project dependencies you need to declare:

Example 28.12. Dependency configurations for project

build.gradle

dependencies {
    compile project(path: ':api', configuration: 'spi')
}

28.3.9. Dependency reports

You can generate dependency reports from the command line (see Section 9.5, “Obtaining information about your build”). With the help of the Project report plugin (see Chapter 27, The Project Report Plugin) such a report can be created by your build.

28.4. Working with dependencies

For the examples below we have the following dependencies setup:

Example 28.13. Configuration.copy

build.gradle

configurations {
    sealife
    alllife.extendsFrom sealife
}

dependencies {
    sealife "sea.mammals:orca:1.0", "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
    alllife "air.birds:albatros:1.0"
}

The dependencies have the following transitive dependencies:

shark-1.0 -> seal-2.0, tuna-1.0

orca-1.0 -> seal-1.0

tuna-1.0 -> herring-1.0

You can use the configuration to access the declared dependencies or a subset of those:

Example 28.14. Accessing declared dependencies

build.gradle

task dependencies << {
    configurations.alllife.dependencies.each { dep -> println dep.name }
    println()
    configurations.alllife.allDependencies.each { dep -> println dep.name }
    println()
    configurations.alllife.allDependencies.findAll { dep -> dep.name != 'orca' }.each { dep -> println dep.name }
}

Output of gradle -q dependencies

> gradle -q dependencies
albatros

albatros
orca
shark
tuna

albatros
shark
tuna

dependencies returns only the dependencies belonging explicitly to the configuration. allDependencies includes the dependencies from extended configurations.

To get the library files of the configuration dependencies you can do:

Example 28.15. Configuration.files

build.gradle

task allFiles << {
    configurations.sealife.files.each { file ->
        println file.name
    }
}

Output of gradle -q allFiles

> gradle -q allFiles
orca-1.0.jar
shark-1.0.jar
seal-2.0.jar
tuna-1.0.jar
herring-1.0.jar

Sometimes you want the library files of a subset of the configuration dependencies (e.g. of a single dependency).

Example 28.16. Configuration.files with spec

build.gradle

task files << {
    configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
        println file.name
    }
}

Output of gradle -q files

> gradle -q files
orca-1.0.jar
seal-2.0.jar

The Configuration.files method always retrieves all artifacts of the whole configuration. It then filters the retrieved files by specified dpendencies. As you can see in the example, transitive dependencies are included.

You can also copy a configuration. You can optionally specify that only a subset of dependencies from the orginal configuration should be copied. The copying methods come in two flavors. The copy method copies only the dependencies belonging explicitly to the configuration. The copyRecursive methode copies all the dependencies, including the dependencies from extended configurations.

Example 28.17. Configuration.copy

build.gradle

task copy << {
    configurations.alllife.copyRecursive { dep -> dep.name != 'orca' }.allDependencies.each { dep ->
        println dep.name
    }
    println()
    configurations.alllife.copy().allDependencies.each { dep ->
        println dep.name
    }
}

Output of gradle -q copy

> gradle -q copy
albatros
shark
tuna

albatros

It is important to note that the returned files of the copied configuration are often but not always the same than the returned files of the dependency subset of the original configuration. In case of version conflicts between dependencies of the subset and dependencies not belonging to the subset the resolve result might be different.

Example 28.18. Configuration.copy vs. Configuration.files

build.gradle

task copyVsFiles << {
    configurations.sealife.copyRecursive { dep -> dep.name == 'orca' }.each { file ->
        println file.name
    }
    println()
    configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
        println file.name
    }
}

Output of gradle -q copyVsFiles

> gradle -q copyVsFiles
orca-1.0.jar
seal-1.0.jar

orca-1.0.jar
seal-2.0.jar

In the example above, orca has a dependency on seal-1.0 whereas shark has a dependency on seal-2.0. The original configuration has therefore a version conflict which is resolved to the newer seal-2.0 version. The files method therefore returns seal-2.0 as a transitive dependency of orca. The copied configuration only has orca as a dependency and therefore there is no version conflict and seal-1.0 is returned as a transitive dependency.

Once a configuration is resolved it is immutable. Changing its state or the state of one of its dependencies will cause an exception. You can always copy a resolved configuration. The copied configuration is in the unresolved state and can be freshly resolved.

To learn more about the API of the configuration class see the javadoc: Configuration .

28.5. Repositories

28.5.1. Introduction

The Gradle repository management, based on Apache Ivy, gives you have a lot of freedom regarding the repository layout and the retrieval policies. Additionally Gradle provides a couple of convenience method to add preconfigured repositories.

28.5.2. Maven repositories

To add the central Maven2 repository (http://repo1.maven.org/maven2) simply type:

Example 28.19. Adding central Maven repository

build.gradle

repositories {
    mavenCentral()
}

Now Gradle looks for your dependencies in this repository.

Quite often certain jars are not in the official Maven repository for licensing reasons (e.g. JTA), but its poms are.

Example 28.20. Adding many Maven repositories

build.gradle

repositories {
    mavenCentral name: 'single-jar-repo', urls: "http://repo.mycompany.com/jars"
    mavenCentral name: 'multi-jar-repos', urls: ["http://repo.mycompany.com/jars1", "http://repo.mycompany.com/jars2"]
}

Gradle looks first in the central Maven repository for the pom and the jar. If the jar can't be found there, its looks for it in the other repositories.

For adding a custom Maven repository you can say:

Example 28.21. Adding custom Maven repository

build.gradle

repositories {
    mavenRepo urls: "http://repo.mycompany.com/maven2"
}

To declare additional repositories to look for jars (like above in the example for the central Maven repository), you can say:

Example 28.22. Adding additional Maven repositories for JAR files

build.gradle

repositories {
    mavenRepo urls: ["http://repo2.mycompany.com/maven2", "http://repo.mycompany.com/jars"]
}

The first URL is used to look for poms and jars. The subsequent URLs are used to look for jars.

28.5.2.1. Accessing password protected Maven repositories

To access a password protected Maven repository (basic authentication) you need to use one of Ivy features:

Example 28.23. Accessing password protected Maven repository

build.gradle

org.apache.ivy.util.url.CredentialsStore.INSTANCE.addCredentials("REALM", "HOST", "USER", "PASSWORD");

Host name should not include "http://" prefix. It is advisable to keep your login and password in gradle.properties rather than directly in the build file.

28.5.3. Flat directory resolver

If you want to use a (flat) filesytem directory as a repository, simply type:

Example 28.24. Flat repository resolver

build.gradle

repositories {
    flatDir name: 'localRepository', dirs: 'lib'
    flatDir dirs: ['lib1', 'lib2']
}

This adds repositories which look into one or more directories for finding dependencies. If you only work with flat directory resolvers you don't need to set all attributes of a dependency. See Section 28.3.7, “Optional attributes”

28.5.4. More about preconfigured repositories

The methods above for creating preconfigured repositories share some common behavior. For all of them, defining a name for the repository is optional. If no name is defined a default name is calculated, depending on the type of the repository. You might want to assign a name, if you want to access the declared repository. For example if you want to use it also for uploading your project artifacts. An explicit name might also be helpful when studying the debug output.

The values passed as arguments to the repository methods can be of any type, not just String. The value that is actually used, is the toString result of the argument object.

28.5.5. Cache

When Gradle downloads dependencies from remote repositories it stores them in a local cache located at USER_HOME/.gradle/cache. When Gradle downloads dependencies from one of its predefined local resolvers (e.g. Flat Directory resolver), the cache is not used as an intermediate storage for dependency artifacts. The cache is always used for caching module descriptors.

28.5.6. More about Ivy resolvers

Gradle, thanks to Ivy under its hood, is extremely flexible regarding repositories:

  • There are many options for the protocol to communicate with the repository (e.g. filesystem, http, ssh, ...)

  • Each repository can have its own layout.

Let's say, you declare a dependency on the junit:junit:3.8.2 library. Now how does Gradle find it in the repositories? Somehow the dependency information has to be mapped to a path. In contrast to Maven, where this path is fixed, with Gradle you can define a pattern that defines what the path will look like. Here are some examples: [22]

// Maven2 layout (if a repository is marked as Maven2 compatible, the organization (group) is split into subfolders according to the dots.)
someroot/[organisation]/[module]/[revision]/[module]-[revision].[ext]

// Typical layout for an ivy repository (the organization is not split into subfolder)
someroot/[organisation]/[module]/[revision]/[type]s/[artifact].[ext]

// Simple layout (the organization is not used, no nested folders.)
someroot/[artifact]-[revision].[ext]

To add any kind of repository (you can pretty easy write your own ones) you can do:

Example 28.25. Definition of a custom repository

build.gradle

repositories {
    add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
        name = 'repo'
        addIvyPattern "$projectDir/repo/[organisation]/[module]-ivy-[revision].xml"
        addArtifactPattern "$projectDir/repo/[organisation]/[module]-[revision](-[classifier]).[ext]"
        descriptor = 'optional'
        checkmodified = true
    }
}

An overview of which Resolvers are offered by Ivy and thus also by Gradle can be found here. With Gradle you just don't configure them via XML but directly via their API.

28.6. Strategies for transitive dependency management

Many projects rely on the Maven2 repository. This is not without problems.

  • The IBibilio repository can be down or has a very long response time.

  • The pom.xml's of many projects have wrong information (as one example, the pom of commons-httpclient-3.0 declares JUnit as a runtime dependency).

  • For many projects there is not one right set of dependencies (as more or less imposed by the pom format).

If your project relies on the IBibilio repository you are likely to need an additional custom repository, because:

  • You might need dependencies that are not uploaded to IBibilio yet.

  • You want to deal properly with wrong metadata in a IBibilio pom.xml.

  • You don't want to expose people who want to build your project, to the downtimes or sometimes very long response times of IBibilio.

It is not a big deal to set-up a custom repository. [23] But it can be tedious, to keep it up to date. For a new version, you have always to create the new XML descriptor and the directories. And your custom repository is another infrastructure element which might have downtimes and needs to be updated. To enable historical builds, you need to keep all the past libraries and you need a backup. It is another layer of indirection. Another source of information you have to lookup. All this is not really a big deal but in its sum it has an impact. Repository Manager like Artifactory or Nexus make this easier. But for example open source projects don't usually have a host for those products.

This is a reason why some projects prefer to store their libraries in their version control system. This approach is fully supported by Gradle. The libraries can be stored in a flat directory without any XML module descriptor files. Yet Gradle offers complete transitive dependency management. You can use either client module dependencies to express the dependency relations, or artifact dependencies in case a first level dependency has no transitive dependencies. People can check out such a project from svn and have everything necessary to build it.

If you are working with a distributed version control system like Git you probably don't want to use the version control system to store libraries as people check out the whole history. But even here the flexibility of Gradle can make your life easier. For example you can use a shared flat directory without XML descriptors and yet you can have full transitive dependency management as described above.

You could also have a mixed strategy. If your main concern is bad metadata in the pom.xml and maintaining custom XML descriptors, Client Modules offer an alternative. But you can of course still use Maven2 repo and your custom repository as a repository for jars only and still enjoy transitive dependency management. Or you can only provide client modules for pom's with bad metadata. For the jars and the correct pom's you still use the remote repository.

28.6.1. Implicit transitive dependencies

There is another way to deal with transitive dependencies without XML descriptor files. You can do this with Gradle, but we don't recommend it. We mention it for the sake of completeness and comparison with other build tools.

The trick is to use only artifact dependencies and group them in lists. That way you have somehow expressed, what are your first level dependencies and what are transitive dependencies (see Section 28.3.7, “Optional attributes”). But the draw-back is, that for the Gradle dependency management all dependencies are considered first level dependencies. The dependency reports don't show your real dependency graph and the compile task uses all dependencies, not just the first level dependencies. All in all, your build is less maintainable and reliable than it could be when using client modules. And you don't gain anything.



[19] JSR 294: Improved Modularity Support in the JavaTM Programming Language, http://jcp.org/en/jsr/detail?id=294

[20] Gradle supports partial multiproject builds (seeChapter 31, Multi-project Builds).

[22] At http://ant.apache.org/ivy/history/latest-milestone/concept.html you can learn more about ivy patterns.

[23] If you want to shield your project from the downtimes of IBibilio things get more complicated. You probably want to set-up a repository proxy for this. In an enterprise environment this is rather common. For an open source project it looks like overkill.