Ivy: Module Descriptors & Configurations

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Apache Ivy 2.6.0 running inside Apache Ant 1.10.18. Every example was executed on JDK 26 on 2026-10-16, on macOS, in a scratch directory with its own Ivy cache and a local file repository, resolving real modules from Maven Central. Output is shown as printed, with the long scratch path shortened to <scratch> and the machine name to <host>; where a listing is trimmed the text says what was removed. The commands use a POSIX shell.

The module descriptor, ivy.xml, is the one file that Ivy cannot do without. It names the module, says in which ways the module can be used (its configurations), lists the files it publishes, and lists what it depends on and which configuration of each dependency it needs in which of its own. This page goes through it element by element. The idea of a descriptor and the cycle that uses it are on the Ivy: Overview page; where the revisions in the rev attribute come from and how conflicts between them are decided is the subject of Ivy: Conflicts & Versions; the ivysettings.xml that tells Ivy where to look is on Ivy: Settings & Resolvers; and publishing with ivy:deliver and ivy:publish is on Ivy: Publishing & Multi-Project Builds. The running scenario (tidepool) is described on the section landing page; the language-neutral ideas (coordinates, scopes, mediation) are in Dependency Management Fundamentals.

How the examples were run

Three small modules were built from the tidepool sources: tidepool-model (no dependencies of its own), tidepool-engine (depends on the model and on org.apache.commons:commons-csv) and tidepool-cli (depends on the engine and on info.picocli:picocli). tidepool-model and tidepool-engine were published with ivy:publish into a file repository repo/ next to the project, so that the next module could depend on them by coordinate. The settings file that lets Ivy see both that repository and Maven Central is below; its elements are explained on the settings page.

<ivysettings>
  <settings defaultResolver="main"/>
  <resolvers>
    <chain name="main">
      <filesystem name="local-repo">
        <ivy pattern="${repo.dir}/[organisation]/[module]/[revision]/ivy.xml"/>
        <artifact pattern="${repo.dir}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"/>
      </filesystem>
      <ibiblio name="central" m2compatible="true"/>
    </chain>
  </resolvers>
</ivysettings>

Most of the experiments are small descriptors resolved by one generic build file, which resolves a descriptor named on the command line and then retrieves the result into one directory per configuration; the lists of files in the output below are ls of those directories. The (-[classifier]) in the pattern is an optional part that appears only for artifacts that have a Maven classifier.

<project name="exp" default="show" xmlns:ivy="antlib:org.apache.ivy.ant">
  <property name="repo.dir" location="${basedir}/../repo"/>
  <ivy:settings file="${basedir}/../ivysettings.xml"/>

  <target name="show">
    <delete dir="out"/>
    <ivy:resolve file="${descriptor}"/>
    <ivy:retrieve pattern="out/[conf]/[artifact]-[revision](-[classifier]).[ext]" log="quiet"/>
  </target>
</project>

It is run as ant -lib ivy-2.6.0.jar -Ddescriptor=m1.xml. In every output block the two banner lines (Apache Ivy 2.6.0 ... and loading settings ...) and the Buildfile, BUILD SUCCESSFUL and timing lines are removed, because they never change; the Ivy cache was already warm (no download lines). Everything else is as printed.

The files in this section are this page’s own setup for the examples that follow, not taken from the documentation.

The shape of a descriptor

An Ivy file is an XML file, usually named ivy.xml. The simplest one has a root and an info:

<ivy-module version="2.0">
  <info organisation="myorg" module="mymodule"/>
</ivy-module>

The root, ivy-module, has a required version (still 2.0 with Ivy 2.6.0). Its children are, in the order the XML schema requires, info (once), then optionally configurations, publications, dependencies and the deprecated conflicts. The same shape, with the children expanded, is the hierarchical index of the reference:

ivy-module
  info             license, ivyauthor, repository, description, extends
  configurations   conf, include
  publications     artifact (conf)
  dependencies     dependency (conf, artifact, include, exclude), exclude, override, conflict
  conflicts        manager

Ivy expects no XML namespace for its own elements and attributes, so you cannot use a prefix for them; your own extra attributes and elements live in a namespace of your own (the info, conf, dependency and artifact elements accept extra attributes, since 1.4). For editor completion, the schema (ivy.xsd, which is inside the JAR at org/apache/ivy/plugins/parser/xml/ivy.xsd) can be declared as xsi:noNamespaceSchemaLocation.

A descriptor plays two roles. During development it is dynamic: it may use dynamic revisions such as latest.release and Ivy variables. At publication it must be resolved: fixed revisions and no variables, so that the published revision always produces the same result. ivy:deliver turns the first kind into the second; both are shown below.

This is the complete descriptor of tidepool-engine, the one used for most of the page (the comments are explained in the sections that follow):

<ivy-module version="2.0">
  <info organisation="io.tidepool" module="tidepool-engine" revision="1.0.0" status="integration">
    <description>Reads stations from CSV and computes tide heights.</description>
  </info>
  <configurations defaultconfmapping="compile->compile(*),master(*);runtime->runtime(*),master(*);test->runtime(*),master(*)"
                  confmappingoverride="true">
    <conf name="master" description="the artifact published by this module, nothing else"/>
    <conf name="compile" description="needed to compile the module and its consumers"/>
    <conf name="runtime" extends="compile" description="needed to run the module"/>
    <conf name="test" extends="runtime" visibility="private" description="needed to compile and run the tests"/>
  </configurations>
  <publications>
    <artifact name="tidepool-engine" type="jar" conf="master,compile"/>
  </publications>
  <dependencies>
    <dependency org="io.tidepool" name="tidepool-model" rev="1.0.0" conf="compile"/>
    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="compile"/>
    <dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3" conf="test"/>
  </dependencies>
</ivy-module>

Resolved (first resolve, with the middle of the list trimmed):

[ivy:resolve] :: resolving dependencies :: io.tidepool#tidepool-engine;1.0.0
[ivy:resolve] 	confs: [master, compile, runtime, test]
[ivy:resolve] 	found io.tidepool#tidepool-model;1.0.0 in local-repo
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	found commons-io#commons-io;2.20.0 in central
[ivy:resolve] 	found commons-codec#commons-codec;1.19.0 in central
[ivy:resolve] 	found org.junit.jupiter#junit-jupiter;6.1.3 in central
  ... (the JUnit modules, 8 more lines)
	---------------------------------------------------------------------
	|                  |            modules            ||   artifacts   |
	|       conf       | number| search|dwnlded|evicted|| number|dwnlded|
	---------------------------------------------------------------------
	|      master      |   0   |   0   |   0   |   0   ||   0   |   0   |
	|      compile     |   4   |   4   |   4   |   0   ||   4   |   4   |
	|      runtime     |   4   |   4   |   4   |   0   ||   4   |   4   |
	|       test       |   13  |   13  |   13  |   0   ||   13  |   13  |
	---------------------------------------------------------------------

The compile configuration holds four modules: the model (from the local repository), commons-csv, and the two modules commons-csv needs. runtime holds the same four, because the commons-csv POM has no runtime-only dependency. test adds JUnit and its dependencies (nine more modules), and master holds nothing, because it exists only to name the module’s own artifact. The rest of the page explains why each of those numbers is what it is.

info

info identifies the module and carries its metadata.

Attribute Meaning Default

organisation

Owner of the module.

required

module

Name of the module.

required

branch

Branch of the module (since 1.4).

the default branch setting, or none

revision

Revision of the module.

required in a repository’s ivy.xml, optional in a file you resolve

status

integration, milestone or release by default (configurable in the settings).

integration

publication

Publication date, yyyyMMddHHmmss. Written by ivy:deliver and ivy:publish.

none

When a descriptor has no revision, Ivy gives the module the pseudo-revision working@<hostname>: that is the working@<host> you see in the output of the experiments below, for instance io.tidepool#exp-mapping;working@<host>. The status matters for dynamic revisions: latest.release selects the latest revision whose status is release, and Ivy’s ivy:publish and ivy:deliver take a status of their own at publication.

Child elements: license (name and URL, the licence of the module), ivyauthor, repository (where the module can be found publicly), description (free text with an optional homepage), and extends (inherit from a parent descriptor, below). After the description you can add elements in your own namespace. The descriptors that Ivy derives from POMs fill license and description from the POM; the converted commons-csv descriptor in the cache has, for instance, <license name="Apache-2.0" url="https://www.apache.org/licenses/LICENSE-2.0.txt"/> and a description with the project’s homepage.

extends: inheriting a parent descriptor

Since 2.2 a descriptor can inherit from another, so that the modules of a project share configurations and dependencies. This is what a Maven parent POM gives you, and what a Gradle convention plugin gives you for build logic. The parent below holds the configuration scheme and the test dependency for every tidepool module:

<ivy-module version="2.0">
  <info organisation="io.tidepool" module="tidepool-parent" revision="1.0.0" status="release"/>
  <configurations defaultconfmapping="compile->default;runtime->default;test->default">
    <conf name="compile"/>
    <conf name="runtime" extends="compile"/>
    <conf name="test" extends="runtime" visibility="private"/>
  </configurations>
  <dependencies>
    <dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3" conf="test"/>
  </dependencies>
</ivy-module>

A child names it in info, with location pointing at the parent file for development builds (when the parent is also published, the location is only a shortcut; if the file is missing, Ivy finds the parent through the resolvers like any dependency):

<ivy-module version="2.0">
  <info organisation="io.tidepool" module="exp-child" revision="1.0.0">
    <extends organisation="io.tidepool" module="tidepool-parent" revision="1.0.0" location="parent/ivy.xml"/>
  </info>
  <dependencies>
    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="compile"/>
  </dependencies>
</ivy-module>
[ivy:resolve] :: resolving dependencies :: io.tidepool#exp-child;1.0.0
[ivy:resolve] 	confs: [compile, runtime, test]
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	found commons-io#commons-io;2.20.0 in central
[ivy:resolve] 	found commons-codec#commons-codec;1.19.0 in central
[ivy:resolve] 	found org.junit.jupiter#junit-jupiter;6.1.3 in central
  ... (the JUnit modules, 8 more lines)
	|      compile     |   3   |   0   |   0   |   0   ||   3   |   0   |
	|      runtime     |   3   |   0   |   0   |   0   ||   3   |   0   |
	|       test       |   12  |   0   |   0   |   0   ||   12  |   0   |

compile: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0
runtime: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0
test: apiguardian-api-1.1.2 commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0 jspecify-1.0.0 junit-jupiter-6.1.3 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0

The child declares no configurations of its own, yet it resolves compile, runtime and test, and gets the test dependency, from the parent. The extendType attribute chooses what is inherited: info, description, configurations, dependencies, licenses or all (the default). The child’s own value wins when an attribute is on both sides. What ivy:deliver publishes is a descriptor with the parent inlined, so that consumers never need the parent; this is the delivered file of the child above, from a run of the deliver task:

<ivy-module version="2.0">
  <info organisation="io.tidepool" module="exp-child" revision="1.0.0" status="release" publication="20261016120512">
    <!-- <extends organisation="io.tidepool" module="tidepool-parent" revision="1.0.0" location="parent/ivy.xml"/> -->
  </info>
  <configurations defaultconfmapping="compile->default;runtime->default;test->default">
    <!-- configurations inherited from io.tidepool#tidepool-parent;1.0.0 -->
    <conf name="compile" visibility="public"/>
    <conf name="runtime" visibility="public" extends="compile"/>
    <conf name="test" visibility="private" extends="runtime"/>
  </configurations>

  <dependencies defaultconfmapping="compile->default;runtime->default;test->default">
    <!-- dependencies inherited from io.tidepool#tidepool-parent;1.0.0 -->
    <dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3" conf="test->default"/>
    <!-- dependencies inherited end -->

    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="compile"/>
  </dependencies>
</ivy-module>

The parent’s dependencies come first in the delivered file: 2.6.0 changed deliver and publish to write inherited dependencies first, to keep the order that resolution used (IVY-1656), and fixed deliver so that it also replaces dynamic revisions inherited from a parent (IVY-1410) and merges the XML namespaces of a parent module (IVY-1658). Since 2.5, attributes that come through extends are added to both configurations and dependencies on delivery.

Configurations

A configuration is the idea that makes Ivy different from Maven’s fixed scopes. The reference defines it as a way to use or construct a module: the same module has different dependencies depending on how you use it, and each distinct set is a configuration. Hibernate’s jta.jar is needed only outside an application server; a build needs the compiler plug-in at build time and not at run time. A configuration is a view of the module that selects some of its artifacts and some of its dependencies. Everything else in Ivy (resolving a part, retrieving a part, mapping to a dependency’s part) is expressed in configurations. If you declare none, the module has one public configuration called default, and that is enough for a simple module.

The configurations element contains conf elements (and include`s). A `conf has these attributes:

Attribute Meaning Default

name

Name of the configuration.

required

description

Free text.

none

visibility

public: other modules may depend on this configuration. private: used only inside the module.

public

extends

Comma-separated list of configurations of the same module that this one extends.

none

transitive

false resolves every dependency of this configuration without its own dependencies (since 1.4).

true

deprecated

Date, yyyyMMddHHmmss, from which the configuration is deprecated.

not deprecated

extends and the wildcards

When configuration B extends A, everything required in A is also required in B; the reverse is not true. This is what lets a test configuration be "everything in runtime, plus the test libraries". The extends attribute accepts wildcards since 1.4: * (all the other configurations), *(public) and *(private). The tidepool scheme in the engine descriptor above is:

flowchart BT runtime["runtime"] -- extends --> compile["compile"] test["test (private)"] -- extends --> runtime master["master (the module's own jar)"] compile -.-> model["tidepool-model"] compile -.-> csv["commons-csv"] test -.-> junit["junit-jupiter"]

The solid arrows are extends; the dashed ones are dependencies declared in the configuration they start from. runtime therefore has the model and commons-csv without declaring them, and test has all three. master stands apart on purpose; it is explained below.

Visibility

A public configuration can be asked for by another module; a private one cannot. Here tidepool-cli tries to depend on the engine’s private test configuration (descriptor v1.xml, with conf="compile->test"):

[ivy:resolve] :: resolving dependencies :: io.tidepool#exp-private;working@<host>
[ivy:resolve] 	confs: [compile]
[ivy:resolve] 	found io.tidepool#tidepool-engine;1.0.0 in local-repo
[ivy:resolve] :: resolution report :: resolve 21ms :: artifacts dl 0ms
	---------------------------------------------------------------------
	|                  |            modules            ||   artifacts   |
	|       conf       | number| search|dwnlded|evicted|| number|dwnlded|
	---------------------------------------------------------------------
	|      compile     |   1   |   0   |   0   |   0   ||   0   |   0   |
	---------------------------------------------------------------------
[ivy:resolve]
[ivy:resolve] :: problems summary ::
[ivy:resolve] :::: WARNINGS
[ivy:resolve] 		::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] 		::          UNRESOLVED DEPENDENCIES         ::
[ivy:resolve] 		::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] 		:: io.tidepool#tidepool-engine;1.0.0: configuration not public in io.tidepool#tidepool-engine;1.0.0: 'test'. It was required from io.tidepool#exp-private;working@<host> compile
[ivy:resolve] 		::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve]
[ivy:resolve] :: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS

BUILD FAILED
<scratch>/exp/build.xml:8: impossible to resolve dependencies:
	resolve failed - see output for details

Visibility is how the engine keeps JUnit out of everyone else’s classpath: its test configuration is private, so no consumer can ask for the test libraries and they never appear in what a consumer gets from compile or runtime. (The Maven counterpart is test scope, which is never transitive; the Gradle counterpart is that testImplementation is not part of the published variants.)

transitive on a configuration

transitive="false" on a conf makes every dependency resolved for that configuration come without its own dependencies, including the ones it gets from extended configurations, and only for that configuration; a configuration that extends it with transitivity on gets transitive dependencies again. The reference’s use case is a compile configuration that forces you to declare every direct dependency, with no risk of forgetting one that happens to arrive transitively. The effect of the same switch on a single dependency is shown under transitive below; the configuration-level form was not run separately here.

The Maven scopes seen as configurations

When Ivy reads a POM it creates a fixed set of configurations for it. This is the list, read from the cached, converted descriptor of commons-csv in the first page’s run (descriptions shortened):

Configuration Content

master

The artifact the module itself publishes, with no dependencies.

compile

The compile-scope dependencies. Not the module’s own JAR.

provided

Like compile, but expected from the JDK or a container; not transitive.

runtime

Dependencies needed to run but not to compile.

test

Dependencies needed only for tests.

system

Like provided, with an explicit JAR; not looked up in a repository.

default

Extends runtime and master: the module’s own JAR and its runtime dependencies. What you get if you ask for nothing special.

sources, javadoc

The source and Javadoc artifacts, if any.

optional

All optional dependencies.

Two facts in that table cause most of the mistakes people make with Maven modules in Ivy. The first: compile does not contain the module’s own JAR, master does, and default combines runtime with master. The second: the * wildcard on the right side of a mapping includes test, optional, sources and javadoc too. Both are demonstrated in the mappings section, and the Maven side of the same story is in Maven dependencies, scopes and mediation.

Ivy configuration (from a POM) Maven scope Gradle (Java plugin)

compile

compile

compileClasspath through api and implementation

runtime

runtime

runtimeClasspath

provided

provided

compileOnly

test

test

testImplementation

master, default

the module’s own artifact

the published variants

The Gradle column is approximate by nature (Gradle’s configurations are not the same idea), and is covered in Gradle: Declaring Dependencies & Configurations.

Configuration defaults: defaultconf, defaultconfmapping and confmappingoverride

Writing conf="compile->compile(*),master(*)" on every dependency is tedious, and three attributes remove the repetition. They sit on configurations or on dependencies; the last definition of each wins.

  • defaultconf (on configurations, since 2.2, or on dependencies, since 1.1) is the mapping used for a dependency that has no conf at all. If it is not set, a dependency with no conf gets *->* (the dependencies reference states this default).

  • defaultconfmapping (since 1.3) changes how a mapping with only a left side is read: for conf="compile", Ivy looks up compile in the defaultconfmapping and uses what it maps to. For compatibility with Ivy 2.1.0, if defaultconf is not set, defaultconfmapping also acts as the default for dependencies with no conf.

  • confmappingoverride (since 1.4): with true, an extending configuration does not inherit the mapping of the configurations it extends but applies its own.

The documentation shows how they interact in a table:

defaultconf defaultconfmapping conf of the dependency Ivy reads it as

none

none

none

*->*

none

none

runtime

runtime->runtime

none

none

test

test->test

runtime

none

none

runtime->runtime

runtime

runtime->*;test->default

none

runtime->*

runtime

runtime->*;test->default

test

test->default

none

runtime->*;test->default

none

runtime->*;test->default

none

runtime->*;test->default

runtime

runtime->*

none

runtime->*;test->default

test

test->default

The table is the one on the configurations page. A descriptor can also pull the whole configurations element from a file with include; the included file has the same structure, with its own defaultconfmapping. The next example, i1.xml, includes inc-confs.xml (which declares compile and runtime and maps both to default), adds test, and sets defaultconf="runtime" on the dependencies:

<configurations defaultconfmapping="compile->default;runtime->default">
  <conf name="compile"/>
  <conf name="runtime" extends="compile"/>
</configurations>
<ivy-module version="2.0">
  <info organisation="io.tidepool" module="exp-include"/>
  <configurations>
    <include file="inc-confs.xml"/>
    <conf name="test" extends="runtime" visibility="private"/>
  </configurations>
  <dependencies defaultconf="runtime">
    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1"/>
    <dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3" conf="test->default"/>
  </dependencies>
</ivy-module>
runtime: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0
test: apiguardian-api-1.1.2 commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0 jspecify-1.0.0 junit-jupiter-6.1.3 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0

There is no listing for compile: commons-csv has no conf, so it got defaultconf, runtime, and only that configuration (and the ones that extend it) receives the dependency. When ivy:deliver publishes an included file, it inlines it, so the delivered descriptor no longer depends on the external file.

confmappingoverride is easiest to see with a dependency whose runtime view is bigger than its compile view, as JUnit Jupiter’s is (the API at compile time, the engine at run time). Both descriptors have defaultconfmapping="compile->compile;runtime->runtime", runtime extends compile, and the dependency is declared once with conf="compile". Without the override, runtime only gets what compile brought; with it, runtime applies its own mapping as well:

confmappingoverride="false":
compile: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
runtime: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0

confmappingoverride="true":
compile: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
runtime: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0

With the default (false) the runtime configuration silently lacks junit-jupiter-engine and junit-platform-engine, a classic way for an Ivy build to compile and then fail at run time. That is why the engine descriptor above sets confmappingoverride="true". The documented in-memory form is conf1->other1;conf2->other2, which is what the reference page shows.

publications

publications lists the files the module publishes and in which public configurations each is found. A consumer who depends on a configuration gets the artifacts published in it (and in the configurations it extends). defaultconf on publications (default *, all public configurations) sets where artifacts go when they name none.

artifact attribute Meaning Default

name

Name of the artifact, without the revision.

the module name

type

Purpose of the artifact (jar, source, doc, …​). Usually, but not necessarily, its extension.

jar

ext

File name extension.

the value of type

conf

Public configurations in which it is published; * is all of them. A nested conf element is equivalent. Do not mix both forms for one artifact.

defaultconf of publications

url

Where the artifact can be found if not in the standard place of the repository (not recommended for an enterprise repository).

none

packaging

Comma-separated packaging types (since 2.4).

none

If the element is missing, the module is taken to publish one artifact, named like the module, in every configuration. A module that publishes no artifact at all (a virtual module that only groups other modules) needs an empty publications element. <artifact/> alone is the default artifact. The tidepool modules publish one JAR, in master (the module alone, no dependencies) and in compile (so that a consumer who maps to compile gets the module’s own JAR together with its compile dependencies):

<publications>
  <artifact name="tidepool-engine" type="jar" conf="master,compile"/>
</publications>

Resolved from tidepool-cli, whose descriptor has conf="compile->compile;runtime->runtime" on the engine, this is why the engine’s own JAR is on the CLI’s classpath without a separate mapping (descriptor and listing below). The CLI’s descriptor, which also has a processor configuration used for the annotation processor, and the result of resolving it:

<ivy-module version="2.0">
  <info organisation="io.tidepool" module="tidepool-cli" revision="1.0.0" status="integration"/>
  <configurations>
    <conf name="compile" description="needed to compile the CLI"/>
    <conf name="processor" description="annotation processors, kept off the compile classpath"/>
    <conf name="runtime" extends="compile" description="needed to run the CLI"/>
    <conf name="test" extends="runtime" visibility="private"/>
  </configurations>
  <dependencies>
    <dependency org="io.tidepool" name="tidepool-engine" rev="1.0.0" conf="compile->compile;runtime->runtime"/>
    <dependency org="info.picocli" name="picocli" rev="4.7.7" conf="compile,processor->default"/>
    <dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3" conf="test->default"/>
  </dependencies>
</ivy-module>
	|      compile     |   6   |   1   |   1   |   0   ||   6   |   1   |
	|     processor    |   1   |   0   |   0   |   0   ||   1   |   0   |
	|      runtime     |   6   |   1   |   1   |   0   ||   6   |   1   |
	|       test       |   15  |   1   |   1   |   0   ||   15  |   1   |

compile: commons-codec-1.19.0.jar commons-csv-1.14.1.jar commons-io-2.20.0.jar picocli-4.7.7.jar tidepool-engine-1.0.0.jar tidepool-model-1.0.0.jar
processor: picocli-4.7.7.jar
runtime: commons-codec-1.19.0.jar commons-csv-1.14.1.jar commons-io-2.20.0.jar picocli-4.7.7.jar tidepool-engine-1.0.0.jar tidepool-model-1.0.0.jar
test: apiguardian-api-1.1.2.jar commons-codec-1.19.0.jar commons-csv-1.14.1.jar commons-io-2.20.0.jar jspecify-1.0.0.jar junit-jupiter-6.1.3.jar junit-jupiter-api-6.1.3.jar junit-jupiter-engine-6.1.3.jar junit-jupiter-params-6.1.3.jar junit-platform-commons-6.1.3.jar junit-platform-engine-6.1.3.jar opentest4j-1.3.0.jar picocli-4.7.7.jar tidepool-engine-1.0.0.jar tidepool-model-1.0.0.jar

(The 1 in the search and dwnlded columns is tidepool-engine itself, which that cache had not seen before; the listing lines are the CLI’s retrieved directories.) Three things worth reading: the engine reached the CLI together with the model and commons-csv, which is transitivity through the compile mapping; JUnit and the engine’s own test configuration are absent from compile and runtime, thanks to the private visibility of the engine’s test; and processor holds only picocli, a configuration that exists so that the processorpath of javac can be fed from it without putting the processor on the compile classpath. That is the Ivy counterpart of Maven’s annotationProcessorPaths and Gradle’s annotationProcessor configuration.

Extra artifacts with a Maven classifier use the extra attribute m:classifier, where m is the namespace http://ant.apache.org/ivy/maven; the converted commons-csv descriptor publishes its sources that way: <artifact name="commons-csv" type="source" ext="jar" conf="sources" m:classifier="sources"/>.

dependencies and dependency

dependencies holds the dependency elements and, after them in this order, the exclude, override and conflict elements; the XSD requires that order. A dependency says: this module needs that module, in that revision, and these configurations of it in these of mine.

Attribute Meaning Default

org

Organisation of the dependency.

the organisation of this module

name

Module name.

required

branch

Branch of the dependency (since 1.4).

the default branch setting

rev

Revision wanted: fixed or dynamic.

required

revConstraint

The dynamic constraint originally used; written by deliver (since 2.0).

the value of rev

conf

The inline configuration mapping.

the defaultconf rules above

force

Tell the conflict manager to use this revision.

false

transitive

Resolve the dependencies of this dependency too (since 1.2).

true

changing

The artifacts of this revision may change without a new revision (since 1.2).

false

The children are conf (a nested form of the mapping), artifact and include (restrict which artifacts are used) and exclude.

Revisions

rev is a fixed revision (1.14.1) or a dynamic one, which Ivy turns into a fixed one while resolving:

Form Selects

latest.integration

The latest revision of the module, whatever its status.

latest.<status>, for instance latest.release

The latest revision with at least that status (since 1.4). Ivy has to read the descriptors from the latest backwards until it finds one, so it can be slow.

1.+

The latest sub-revision with that prefix: with 1.0.3, 1.0.7 and 1.1.2 available, 1.0.+ gives 1.0.7.

[1.0,2.0], [1.0,2.0[, ]1.0,2.0], ]1.0,2.0[, [1.0,), ]1.0,), (,2.0], (,2.0[

A range (since 1.4), in mathematical notation.

a custom matcher

Pluggable (since 1.4).

All four dynamic forms against Maven Central, in four configurations of one descriptor (the first configuration uses rev="1.+"):

[ivy:resolve] :: resolving dependencies :: io.tidepool#exp-dynamic;working@<host>
[ivy:resolve] 	confs: [plus, range, latestrel, latestint]
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	[1.14.1] org.apache.commons#commons-csv;1.+
[ivy:resolve] 	found org.apache.commons#commons-csv;1.11.0 in central
[ivy:resolve] 	[1.11.0] org.apache.commons#commons-csv;[1.9,1.12)
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	[1.14.1] org.apache.commons#commons-csv;latest.release
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	[1.14.1] org.apache.commons#commons-csv;latest.integration

plus: commons-csv-1.14.1
range: commons-csv-1.11.0
latestrel: commons-csv-1.14.1
latestint: commons-csv-1.14.1

The line [1.11.0] org.apache.commons#commons-csv;[1.9,1.12) is Ivy telling you what the dynamic revision became. The range took the highest version in [1.9,1.12); Maven Central has versions 1.9.0, 1.10.0 and 1.11.0 below 1.12.0. Note that the trailing-plus form is a prefix match, not a number comparison: 1.1+ matches 1.10.0 as much as 1.1.3, and in this run it gave 1.14.1:

[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	[1.14.1] org.apache.commons#commons-csv;1.1+

The descriptors Ivy derives from Maven Central POMs carry status="release" (the cached commons-csv descriptor does), so latest.release and latest.integration give the same module on Central. A local module published with status integration would be skipped by latest.release, as the reference says (not run here). Dynamic revisions are why Ivy resolves the same descriptor to different results on different days, and why a published (delivered) descriptor must not contain them. ivy:deliver replaces them and keeps the original in revConstraint. With rev="1.+" in the source:

  <dependencies>
    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" revConstraint="1.+" conf="compile->master"/>
  </dependencies>

That is the line from the delivered file, produced by ivy:deliver after a resolve. It is how Ivy keeps builds reproducible without losing the intent. (2.6.0 fixed deliver so that it replaces dynamic revisions by the revision resolved before conflict resolution, the behaviour of releases before 2.3.0, which matters when several configurations resolve the same module differently: IVY-1485 and IVY-1661.) Details of statuses, matchers and latest strategies are on Ivy: Conflicts & Versions.

Configuration mappings

The conf attribute is where Ivy’s power and its traps are. It is a mapping from configurations of this module (the left side, the "master" configurations) to configurations of the dependency (the right side). Read -> as "depends on".

Syntax Meaning

compile

Same-named configuration of the dependency (unless a defaultconfmapping says otherwise).

compile->default

Dependency’s default is needed in my compile.

A,B->E,F

Both E and F are needed in both A and B.

*->B,C

B and C needed in all my configurations. * on the right means all public configurations of the dependency.

*,!A,!B->X

X in all configurations except A and B (since 1.4).

*->@

Map every configuration to the one with the same name in the dependency (since 1.2).

A->#

Map to the configuration being resolved (since 1.4); like @ but follows extended configurations. The reference’s own advice: if you do not understand it, do not use it.

test->runtime;%->default

% as the left side means "all the other configurations" (since 1.4).

test->runtime(default)

If the dependency has no runtime, use default; if it has neither, it is an error (since 1.3). * can be the fallback.

A->B; *->C

Semicolon: the union of the mappings.

A+B->...

Intersection of configurations (since 2.1).

*[axis=platform]->default

Group of configurations that share an extra attribute value (the reference’s example).

test->[org=A]runtime,[org=B]default

A condition in brackets on the right side (since 1.4).

A nested form is equivalent and verbose; do not mix the two on one dependency. This is n1.xml, equal to the inline compile->compile;runtime->runtime:

<dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3">
  <conf name="compile" mapped="compile"/>
  <conf name="runtime" mapped="runtime"/>
</dependency>
compile: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
runtime: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0

What the right side picks, run for real

Most mapping mistakes are about which configuration of a Maven module you ask for. This descriptor maps one dependency (JUnit Jupiter, an aggregator whose POM has junit-jupiter-api and junit-jupiter-params as compile dependencies, and the engine as runtime) in five different ways, one per configuration of mine:

<dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3"
            conf="c1->master;c2->compile;c3->runtime;c4->default;c5->compile(*),master(*)"/>
[ivy:resolve] :: resolving dependencies :: io.tidepool#exp-mapping;working@<host>
[ivy:resolve] 	confs: [c1, c2, c3, c4, c5]
[ivy:resolve] 	found org.junit.jupiter#junit-jupiter;6.1.3 in central
  ... (the other 8 modules, one line each)
	---------------------------------------------------------------------
	|                  |            modules            ||   artifacts   |
	|       conf       | number| search|dwnlded|evicted|| number|dwnlded|
	---------------------------------------------------------------------
	|        c1        |   1   |   0   |   0   |   0   ||   1   |   0   |
	|        c2        |   7   |   0   |   0   |   0   ||   6   |   0   |
	|        c3        |   9   |   0   |   0   |   0   ||   8   |   0   |
	|        c4        |   9   |   0   |   0   |   0   ||   9   |   0   |
	|        c5        |   7   |   0   |   0   |   0   ||   7   |   0   |
	---------------------------------------------------------------------

c1: junit-jupiter-6.1.3
c2: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
c3: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0
c4: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-6.1.3 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0
c5: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-6.1.3 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0

Reading it row by row, which is the practical rule for Maven modules:

  • ->master (c1): the module’s own JAR and nothing else. One module, one artifact.

  • ->compile (c2): the compile-scope dependencies of the module, without the module itself. Seven modules but six artifacts: the junit-jupiter module is in the graph but its JAR is in master, not in compile, so it has no artifact in this configuration. This is the usual surprise: mapping to compile alone leaves out the library you named.

  • ->runtime (c3): the same without the module itself, plus the runtime-scope dependencies (the engine).

  • ->default (c4): the module itself and all runtime dependencies: everything. The usual choice for a build that wants "the library and what it needs".

  • ->compile(*),master(*) (c5): the module itself and the compile dependencies, without the runtime-only ones. This is the form Ivy itself writes when it converts a POM dependency (you can read it in the converted descriptor: conf="compile->compile(*),master(*);runtime->runtime(*)"), and the right one for a compile classpath. In the listing for c5, junit-jupiter-6.1.3 is the module’s own JAR, and there is no engine.

*->@ maps each configuration to the same-named one of the dependency, and % is "all the others". Run on junit-jupiter with a compile / runtime pair:

<dependency org="org.junit.jupiter" name="junit-jupiter" rev="6.1.3" conf="*->@"/>
compile: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
runtime: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0

and with conf="test->runtime;%->compile" on a module with compile, runtime (extends compile) and test (extends runtime):

compile: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
runtime: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 opentest4j-1.3.0
test: apiguardian-api-1.1.2 jspecify-1.0.0 junit-jupiter-api-6.1.3 junit-jupiter-engine-6.1.3 junit-jupiter-params-6.1.3 junit-platform-commons-6.1.3 junit-platform-engine-6.1.3 opentest4j-1.3.0

Neither *->@ nor % brings the JUnit Jupiter module’s own JAR, for the reason above. A fallback shows up when the named configuration does not exist. With conf="compile->tidepool(compile)" on commons-csv, which has no tidepool configuration, Ivy used compile instead:

compile: commons-codec-1.19.0 commons-io-2.20.0

and, again, got the compile dependencies but not the commons-csv JAR. Without the fallback, conf="compile->tidepool" fails with the message to look for:

[ivy:resolve] 		:: org.apache.commons#commons-csv;1.14.1: configuration not found in org.apache.commons#commons-csv;1.14.1: 'tidepool'. It was required from io.tidepool#exp-missing-conf;working@<host> compile

(The surrounding UNRESOLVED DEPENDENCIES banner and the BUILD FAILED ... resolve failed - see output for details lines are the same as in the visibility error above.)

The intersection, [axis=...] group and [org=...] condition forms are documented and were not run here.

The default mapping trap

A dependency with no conf at all, in a descriptor that declares no configurations, is read as *->*: every public configuration of the dependency in the module’s only configuration, default. For a Maven module, that includes test, optional, sources and javadoc. This is the descriptor:

<ivy-module version="2.0">
  <info organisation="io.tidepool" module="exp-simplest"/>
  <dependencies>
    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1"/>
  </dependencies>
</ivy-module>

and this is what it asked for, trimmed to the table and the retrieved files:

[ivy:resolve] :: resolving dependencies :: io.tidepool#exp-simplest;working@<host>
[ivy:resolve] 	confs: [default]
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	found commons-io#commons-io;2.20.0 in central
[ivy:resolve] 	found commons-codec#commons-codec;1.19.0 in central
[ivy:resolve] 	found org.junit.jupiter#junit-jupiter;5.13.1 in central
[ivy:resolve] 	found org.mockito#mockito-core;4.11.0 in central
[ivy:resolve] 	found com.h2database#h2;2.2.224 in central
[ivy:resolve] 	found org.openjdk.jmh#jmh-core;1.37 in central
[ivy:resolve] 	found com.github.spotbugs#spotbugs-annotations;4.9.3 in central
  ... (14 more lines)
	|      default     |   22  |   0   |   0   |   0   ||   24  |   0   |

default: apiguardian-api-1.1.2 byte-buddy-1.12.19 byte-buddy-agent-1.12.19 commons-codec-1.19.0 commons-csv-1.14.1-javadoc commons-csv-1.14.1-sources commons-csv-1.14.1 commons-io-2.20.0 commons-lang3-3.18.0 commons-math3-3.6.1 h2-2.2.224 jmh-core-1.37 jopt-simple-5.0.4 jsr305-3.0.2 junit-jupiter-5.13.1 junit-jupiter-api-5.13.1 junit-jupiter-engine-5.13.1 junit-jupiter-params-5.13.1 junit-platform-commons-1.13.1 junit-platform-engine-1.13.1 mockito-core-4.11.0 objenesis-3.3 opentest4j-1.3.0 spotbugs-annotations-4.9.3

Twenty-two modules and twenty-four artifacts for a library that needs two: the H2 database, JMH, Mockito, Byte Buddy, JUnit 5.13 (the version in the commons-csv POM, not the 6.1.3 of the other examples) and the sources and Javadoc JARs of commons-csv itself, because * includes the test, optional, sources and javadoc configurations of the converted POM. Ivy did exactly what the descriptor said. The remedy is always an explicit mapping: compile->default for a runtime library, compile->compile(*),master(*) for a compile classpath.

Artifact restriction: artifact and include

For a dependency without an Ivy file (Maven Central modules have one derived from the POM, but a plain file repository may not), or when you need an artifact the module does not declare, you tell Ivy which artifacts to take. artifact names them; include selects among those the module declares. Each can be limited to master configurations. The next descriptor takes the plain JAR in compile and the sources JAR (a Maven classifier artifact) in docs:

<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
  <info organisation="io.tidepool" module="exp-artifact-restriction"/>
  <configurations>
    <conf name="compile"/>
    <conf name="docs"/>
  </configurations>
  <dependencies>
    <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" transitive="false" conf="compile,docs->master">
      <artifact name="commons-csv" type="jar" ext="jar" conf="compile"/>
      <artifact name="commons-csv" type="source" ext="jar" m:classifier="sources" conf="docs"/>
    </dependency>
  </dependencies>
</ivy-module>
	|      compile     |   1   |   0   |   0   |   0   ||   1   |   0   |
	|       docs       |   1   |   0   |   0   |   0   ||   1   |   0   |

compile: commons-csv-1.14.1
docs: commons-csv-1.14.1-sources

An alternative with the same result and no artifact elements is to map the docs configuration to the module’s sources configuration, docs->sources, which the converted POM descriptor defines (it also ran: conf="compile->master;sources->sources" gave commons-csv-1.14.1.jar in compile and commons-csv-1.14.1-sources.jar in sources). The reference warns that, when you give artifact elements for a dependency that declares its own, the artifacts it declares are ignored, and that a configuration with no artifact specification gets the default artifact; map the configurations explicitly, as above.

transitive

transitive="false" on a dependency resolves it without its own dependencies, the Ivy form of Maven’s exclusion of everything or Gradle’s isTransitive = false. Here commons-csv is declared once with transitivity (configuration all) and once without (configuration direct), next to picocli:

<dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="all->default"/>
<dependency org="info.picocli" name="picocli" rev="4.7.7" conf="direct->default"/>
<dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" transitive="false" conf="direct->default"/>
	|        all       |   3   |   0   |   0   |   0   ||   3   |   0   |
	|      direct      |   2   |   0   |   0   |   0   ||   2   |   0   |

all: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0
direct: commons-csv-1.14.1 picocli-4.7.7

Use it when you take a library but not its dependencies, a library that bundles what it needs, or a project where you list the whole classpath yourself. The cost is that nothing is checked: if the library did need its dependencies, the failure comes at run time.

force

When two parts of the graph want different revisions of a module, the conflict manager picks one, latest-revision by default. force="true" on a direct dependency tells the conflict manager to use that revision. commons-csv 1.14.1 wants commons-io 2.20.0; the descriptor also declares commons-io directly at 2.18.0. First without force:

[ivy:resolve] 	confs: [compile]
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	found commons-io#commons-io;2.20.0 in central
[ivy:resolve] 	found commons-codec#commons-codec;1.19.0 in central
[ivy:resolve] :: resolution report :: resolve 126ms :: artifacts dl 3ms
[ivy:resolve] 	:: evicted modules:
[ivy:resolve] 	commons-io#commons-io;2.18.0 by [commons-io#commons-io;2.20.0] in [compile]
	|      compile     |   4   |   0   |   0   |   1   ||   3   |   0   |

compile: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0

The newer one won, although you asked for 2.18.0 directly. Then with force="true" on the direct declaration (<dependency org="commons-io" name="commons-io" rev="2.18.0" force="true" conf="compile->default"/>):

[ivy:resolve] 	confs: [compile]
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	found commons-codec#commons-codec;1.19.0 in central
[ivy:resolve] 	found commons-io#commons-io;2.18.0 in central
[ivy:resolve] :: resolution report :: resolve 49ms :: artifacts dl 2ms
[ivy:resolve] 	:: evicted modules:
[ivy:resolve] 	commons-io#commons-io;2.20.0 by [commons-io#commons-io;2.18.0] in [compile]
	|      compile     |   4   |   0   |   0   |   1   ||   3   |   0   |

compile: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.18.0

force can therefore downgrade as well as upgrade. It is a hint to the conflict manager, not a version pin like Gradle’s strictly; the override below is the pin. The conflict managers, including the new nearest, are on Ivy: Conflicts & Versions.

changing

By default Ivy assumes that a revision never changes; once it has the descriptor and artifacts of foo;1.2 in the cache it does not ask the repository again. changing="true" says that the artifacts of this revision may change without a new revision, the situation of a snapshot. Ivy’s rule for changing revisions, from the reference: if the module metadata has not changed it considers the whole module unchanged; if the descriptor has changed it looks at the publication date to see whether it is a new publication of the same revision; if so, it checks the artifacts' modification times and downloads accordingly. The advice there is to publish with ivy:publish, which updates the publication date. Here is what that looks like in a file repository with a module tidepool-snap;1.0-SNAPSHOT, resolved from two descriptors that differ only in changing:

$ echo "build 1" > repo/.../tidepool-snap-1.0-SNAPSHOT.jar
# resolve with changing="false", then look at what was retrieved
retrieved: build 1
$ echo "build 2" > repo/.../tidepool-snap-1.0-SNAPSHOT.jar   (descriptor untouched)
# resolve with changing="false"
retrieved: build 1
# resolve with changing="true"
retrieved: build 1
$ touch repo/.../ivy.xml
# resolve with changing="false"
retrieved: build 1
# resolve with changing="true"
retrieved: build 2

Two lessons. Changing the artifact alone is not noticed even for a changing dependency; Ivy looks at the descriptor’s modification time first. And a non-changing dependency never notices. The descriptor in the repository was written by hand here; a real snapshot is published with ivy:publish, which sets the publication date, and the resolver should have checkmodified="true" (the default default chain in the built-in settings sets it) so that Ivy checks the repository for a modified descriptor. changingPattern on a resolver marks whole groups of revisions as changing, so you need not set it on every dependency. Both are on Ivy: Settings & Resolvers.

exclude

exclude removes artifacts, modules or organisations from the graph. It has two homes: as a child of a dependency, where it applies to what that dependency brings in, and as a child of dependencies, where it applies to the whole module (since 2.0). Its attributes are org, module, artifact, type, ext, matcher and conf, all defaulting to "anything"; matcher is exact (the default since descriptors of version 1.3) or regexp, and conf limits the exclusion to some of your configurations.

On a dependency, removing commons-codec while keeping commons-csv and commons-io:

<dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="compile->default">
  <exclude org="commons-codec"/>
</dependency>
	|      compile     |   2   |   0   |   0   |   0   ||   2   |   0   |

compile: commons-csv-1.14.1 commons-io-2.20.0

On the whole module, but only in compile, with test unchanged:

<dependencies>
  <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="compile,test->default"/>
  <exclude org="commons-io" module="commons-io" conf="compile"/>
</dependencies>
	|      compile     |   2   |   0   |   0   |   0   ||   2   |   0   |
	|       test       |   3   |   0   |   0   |   0   ||   3   |   0   |

compile: commons-codec-1.19.0 commons-csv-1.14.1
test: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.20.0

The module-level form is the one to use when many dependencies drag in a module you never want (a logging bridge, for instance). Excluding a module is a statement that nothing needs it; if something does, you find out at run time.

override

override changes the revision (and/or branch) requested for a transitive dependency, before any conflict management and without declaring a dependency on it (since 2.0). This is the right tool when a direct dependency brings a module whose version you want to pin, and the module is not really yours to depend on. The override is applied in the phase called dependency descriptor mediation; afterwards the transitive dependency behaves exactly as if it had been declared with the new value. Attributes: org, module, branch, rev, matcher (default exact; org and module can be expressions with a matcher such as regexp).

commons-csv wants commons-io 2.20.0; with <override org="commons-io" module="commons-io" rev="2.19.0"/> after the dependencies:

[ivy:resolve] 	confs: [compile]
[ivy:resolve] 	found org.apache.commons#commons-csv;1.14.1 in central
[ivy:resolve] 	found commons-io#commons-io;2.19.0 in central
[ivy:resolve] 	found commons-codec#commons-codec;1.19.0 in central
	|      compile     |   3   |   1   |   1   |   0   ||   3   |   1   |

compile: commons-codec-1.19.0 commons-csv-1.14.1 commons-io-2.19.0

(The 1 in search and dwnlded is the first download of commons-io 2.19.0 in that cache.) Nothing was evicted, because there was no conflict: the override changed what was requested. In the converted commons-csv descriptor the entries of the BOM that its POM imports (dependencyManagement) appear as override elements; the first is <override org="org.junit.jupiter" module="junit-jupiter" matcher="exact" rev="5.13.1"/>.

Since 2.6.0 the post-resolve tasks (retrieve, cachepath and the others) also accept an override child, so a build can adjust revisions when it gathers files. The child works with the resolve that the task performs itself from nested dependency elements (not in the inline mode, and not with file; the task rejects both with a message such as 'file' not allowed with child elements). This ran:

<ivy:retrieve pattern="out/[artifact]-[revision].[ext]">
  <dependency org="org.apache.commons" name="commons-csv" rev="1.14.1" conf="default"/>
  <override org="commons-io" module="commons-io" rev="2.19.0"/>
</ivy:retrieve>
commons-codec-1.19.0.jar
commons-csv-1.14.1.jar
commons-io-2.19.0.jar

The 2.6.0 release also makes makepom write an override into the generated POM’s dependencyManagement (IVY-1663), so published POMs keep the pin. That one was not run here.

conflict and conflicts

conflict (a child of dependencies, since 2.0) names the conflict manager to use for one or several modules, selected with org, module and matcher, either by manager (the name of a manager) or by rev (a comma-separated list of revisions to select, for a fixed manager). It replaces the old top-level conflicts element, which is deprecated. The default, when nothing is specified, is latest-revision for all modules. The strict manager does the opposite of resolving: it fails if two revisions of a module are required. With the same commons-io 2.18.0 and 2.20.0 from the force example, and <conflict org="commons-io" module="commons-io" manager="strict"/>:

[ivy:resolve] :: problems summary ::
[ivy:resolve] :::: ERRORS
[ivy:resolve] 	commons-io#commons-io;2.20.0 (needed by [org.apache.commons#commons-csv;1.14.1]) conflicts with commons-io#commons-io;2.18.0 (needed by [io.tidepool#exp-strict;working@<host>])
[ivy:resolve]
[ivy:resolve] :: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS

BUILD FAILED
<scratch>/exp/build.xml:8: impossible to resolve dependencies:
	commons-io#commons-io;2.20.0 (needed by [org.apache.commons#commons-csv;1.14.1]) conflicts with commons-io#commons-io;2.18.0 (needed by [io.tidepool#exp-strict;working@<host>])

Ivy conflict managers (latest-revision, latest-time, latest-compatible, strict, all, nearest since 2.6.0, and fixed ones) are the subject of Ivy: Conflicts & Versions. For Maven’s mediation, see Maven dependencies, scopes and mediation; for Gradle’s, Gradle dependency resolution, conflicts and capabilities.

Designing the configurations of a project

A short checklist that follows from the above, and the scheme the tidepool modules use:

  • Start from what the consumer needs. One public configuration per way the module is used (compile, runtime), a master for the module alone, and private ones for what only you need (test, and here a processor).

  • Use extends for "plus". runtime extends compile; test extends runtime. Never repeat a dependency in two configurations when an extends says it.

  • Publish the artifact in the configurations that should deliver it, typically master and compile. Consumers map to compile or runtime and get the module’s JAR with its dependencies.

  • Map explicitly. compile->compile;runtime->runtime between your own Ivy modules, compile->default for a runtime library from Maven Central, compile->compile(*),master(*) for a compile-time one. Never rely on the *->* default.

  • Set confmappingoverride="true" if you use defaultconfmapping with extending configurations whose views of a dependency differ.

  • Keep dynamic revisions out of published descriptors; ivy:deliver does it for you.

The cross-tool picture is in Comparing Ant, Maven & Gradle. The tasks that act on all of this (resolve, retrieve, cachepath, report, deliver, publish) are on Ivy Ant Tasks.

Common problems

Symptom Cause Fix

configuration not found in org.apache.commons#commons-csv;1.14.1: 'tidepool'

The right side of a mapping names a configuration the dependency does not have.

Use default, compile, runtime for POM modules, or a fallback compile->tidepool(compile).

configuration not public in io.tidepool#tidepool-engine;1.0.0: 'test'

The right side names a private configuration.

Map to a public one, or make the configuration public.

22 modules and Mockito for a small library

No conf, so *->*.

Map explicitly.

Compiles, but the library’s own JAR is missing from the classpath

Mapped to compile or runtime, which exclude the module’s own JAR.

Map to default or to compile(*),master(*).

Compiles, then fails at run time with a missing engine class

runtime extends compile without confmappingoverride, so runtime only has the compile view.

confmappingoverride="true", or map runtime explicitly.

The same descriptor resolves differently next month

Dynamic revisions (1.+, latest.release).

Fixed revisions, or ivy:deliver at publication.

A snapshot is not refreshed

Not changing, or the descriptor was not touched.

changing="true", a resolver with checkmodified, publish with ivy:publish.

ivy.xml fails validation

The order of children is wrong (dependency before exclude before override before conflict).

Follow the schema.