Moving to Maven from an ancient Ant build

This posting initiates the series on moving from an Ant to a Maven build. If you are interested in my help with your Ant to Maven transition contact me at andrew@andrewgilmartin.com.

You use Ant to build your system of one or more deliverables. You have one build.xml file. You have toyed with separating out the deliverables into individual build.xml files, but there seemed not to be enough value in doing so. Over time your build.xml has actually had less unique things to do, eg RMI no longer needs a compile step, you moved database initialization and configuration into another tool, and the newer React web front end is built elsewhere. Effectively the build.xml just complies the source, runs the tests, and assembles one or more jars or wars.

The Ant build while now simpler has a number of downsides. You still manually manage the large set of external libraries. Perhaps those libraries have not been updated in years as it is too cumbersome to track down their origin and incorporate the most recent, viable version. Your IDE does not integrate well with your bespoke build environment. The IDE's tools for creating test classes, running tests, showing library JavaDoc, navigating into library sources, and code completion is ineffective or unusable.

It would be better all-round if you moved to Maven. This series of postings is an approach to accomplishing that.

If you have not yet installed Maven then do so from

https://maven.apache.org/install.html

The first stage to moving to a Maven build is to replace the external libraries with Maven dependencies. You don't need to replace Ant to do this thanks to the Maven Ant Tasks

https://maven.apache.org/ant-tasks/

Download the most recent jar and place it in your build libraries. Now add the following Ant task definition to your build.xml (near the top of the file)

<taskdef
  name="mvn-dependencies"
  classname="org.apache.maven.artifact.ant.DependenciesTask"
  classpath="${basedir}/buildlib/maven-ant-tasks-2.1.3.jar"
  onerror="report"/>

Update the "classpath" attribute as necessary. Do a clean build to ensure that the "mvn-dependencies" task is accessible.

The Ant build likely has different and/or overlapping external libraries for building, testing, and running. You will be replacing these with equivalent dependencies declared in <mvn-dependencies/> elements. Add the following elements to your build.xml (after the <taskdef/>)

<mvn-dependencies
  pathId="build-dependencies.classpath"
  sourcesFilesetId="build-dependencies-sources.classpath"
  javadocFilesetId="build-dependencies-javadoc.classpath"
  settingsfile="${basedir}/maven-settings.xml" >
  <!--
  <dependency groupId="" artifactid="" version=""/>
  -->
</mvn-dependencies>

<mvn-dependencies
  pathId="test-dependencies.classpath"
  sourcesFilesetId="test-dependencies-sources.classpath"
  javadocFilesetId="test-dependencies-javadoc.classpath"
  settingsfile="${basedir}/maven-settings.xml" >
  <!--
  <dependency groupId="" artifactid="" version=""/>
  -->
</mvn-dependencies>

<mvn-dependencies
  pathId="runtime-dependencies.classpath"
  sourcesFilesetId="runtime-dependencies-sources.classpath"
  javadocFilesetId="runtime-dependencies-javadoc.classpath"
  settingsfile="${basedir}/maven-settings.xml" >
  <!--
  <dependency groupId="" artifactid="" version=""/>
  -->
</mvn-dependencies>

The "pathId" is going to be used in conjunction with your existing <path/> elements for constructing the classpaths used in building, testing, and running. Eg,

<path id="build.classpath">
  <path refid="runtime-dependencies.classpath"/>
  <path refid="build-dependencies.classpath"/>
  <!-- ... -->
</path>

<path id="test.classpath">
  <path refid="runtime-dependencies.classpath"/>
  <path refid="build-dependencies.classpath"/>
  <path refid="test-dependencies.classpath"/>
  <!-- ... -->
</path>

<path id="runtime.classpath">
  <path refid="runtime-dependencies.classpath"/>
  <!-- ... -->
</path>

The <mvn-dependencies/> "settingsfile" attribute references a Maven configuration. This file is used to specify the Maven repository managers, ie where dependences can be found. You should place this file under version control alongside the build.xml. The base file is

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <profiles>
    <profile>
      <id>default</id>
      <repositories>
        <!--
        <repository>
          <id></id>
          <url></url>
        </repository>
        -->
      </repositories>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>default</activeProfile>
  </activeProfiles>
</settings>

You are now ready to use Maven dependencies. For each external library you will need to know its name, version, and, if possible, origin URL. To find these search the Maven repository index at

https://mvnrepository.com/

Eg, if you are using Apache Codec at runtime then search for "apache codec" and navigate to the dependency version page

https://mvnrepository.com/artifact/commons-codec/commons-codec/1.8

You will need to record the group id, artifact id, version, and repository URL. Get the group id, artifact id, and version from the Maven tab, ie

<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.8</version>
</dependency>

Add this to the runtime <mvn-dependencies/> element in build.xml, eg

<dependency groupId="commons-codec" artifactid="commons-codec" version="1.8"/>

Get the repository URL from the Repositories field, ie following the "Central" link reveals that the URL is

https://repo1.maven.org/maven2/

If the repository URL is unique then update maven-settings.xml, eg under <repositories/> add

<repository>
  <id>Central</id>
  <url>https://repo1.maven.org/maven2/</url>
</repository>

Now delete the external library's jar. Do a clean build to test that you have correctly replaced the dependency.

Continue replacing external libraries with dependencies until you are done. It has been my experience that this will progress far quicker than you initially worried it would.

You may find a few external libraries that were themselves never built with Maven or were never deposited to a repository. For these you will need to continue to use the manually managed jar. Your best option moving forward is to replace the library with a functional equivalent. That task is for another day, however.

If you have now emptied your directories of external libraries then remove their reference from your <path/> elements (or <javac/>, etc).

A word of warning. It is very tempting to replace the external library with a newer dependency. Don't do it. Your task right now is only to replace external libraries with equivalent dependencies. Simply make note that a newer version exists.