Maven notes

Russell Bateman
2009
last update:



Also see old Maven notes.

There is probably no better—easier to read, clearer to understand—exposé than Baeldung's short Apache Maven Tutorial.

See How to Publish Your Artifacts to Maven Central


What is Maven?

Maven comes from Yiddish meyvn, "one who understands." Extrapolated due to usage, a maven is someone who is an expert.

Apache Maven is an Apache Software Foundation project that is used primarily to build and manage Java-based projects, but it can be used for C#, Ruby, Scala and many other languages.

Maven is basically a plug-in execution framework. Everthing that happens in the build process happens because some plug-in accomplishes it. There is a core if limited set of plug-ins provided when Maven is installed, but there are also important third-party plug-ins.

Any time a developer nees to perform custom tasks just a bit out of the ordinary, such as create WAR or NAR files, often it's a third-party developer that wrote it.


Installation

(It's probably best to defer to the installation instructions which were recorded long after these.)

  1. Download from maven.apache.org.
  2. Extract the download to where you would like to keep it. Maven is a more of a personal tool, so maybe local to your user rather than to a host-wide path.
  3. Set up the M2_HOME variable in your .profile to point to the root of the installation. Also, add variable M2 to point to the bin subdirectory, i.e.:
    $ export M2_HOME=~/dev/apache-maven/apache-maven-3.1.1
    $ export M2=${M2_HOME}/bin
    
  4. Add MAVEN_OPTS if you wish to override JVM properties. (See Maven download page.)
  5. Add what's in M2 to your path if you plan on using Maven from the command line a lot.
    $ PATH=${PATH}:${M2}
    
  6. Ensure that JAVA_HOME is set to the root of your Java installation and that $JAVA_HOME/bin is on your $PATH.
  7. Run mvn --version to verify correct installation.

Install latest Maven on Linux

This assumes you've set up your user to consume a valid JDK with JAVA_HOME set up because you're using (Eclipse, IntelliJ IDEA, etc.).

  1. Go to Downloading Apache Maven x.y.z. Download using the link apache-maven-x.y.z-bin.tar.gz. Let's say you drop this tarball on /home/russ/Downloads.

  2. Since this will be for the entire host, go to /opt.
    $ sudo bash
    # cd /opt
    
  3. Explode the tarball at /opt:
    /opt # tar -zxf /home/russ/Downloads/apache-maven-x.y.z-bin.tar.gz
    
  4. Edit etc/environment to add
    M2_HOME=/opt/apache-maven-3.8.1
    
  5. Append Maven's /bin subdirectory to PATH in that file; should look something like this when you've finished:
    PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/opt/apache-maven-3.8.1/bin"
    
  6. Now fix up your host's alternatives:
    # update-alternatives --install "/usr/bin/mvn" "mvn" "/opt/apache-maven-x.y.z/bin/mvn"
    # update-alternatives --set mvn /opt/apache-maven-x.y.z/bin/mvn
    
  7. Optionally, if you want Bash completion to help you out with tab-completion of some complex Maven command (at the command line), do this:
    # wget https://raw.github.com/dimaj/maven-bash-completion/master/bash_completion.bash --output-document /etc/bash_completion.d/mvn
    
  8. Check the version; you may need to log out, then -in again:
    $ mvn --version
    Apache Maven 3.6.3
    Maven home: /usr/share/maven
    Java version: 11.0.10, vendor: AdoptOpenJDK, runtime: /home/russ/dev/jdk-11.0.10+9
    Default locale: en_US, platform encoding: UTF-8
    OS name: "linux", version: "5.4.0-74-generic", arch: "amd64", family: "unix"
    

Creating a project
  1. Create a subdirectory somewhere.
    ~/dev $ mkdir project
    
  2. Execute this Maven goal:
    $ mvn archetype:generate -DgroupId=com.etretatlogiciels.project \
                             -DartifactId=project \
                             -DarchetypeArtifactId=maven-archetype-quickstart \
                             -DinteractiveMode=false
    

What this means...

groupId the package path root (your domain)
artifactId the project (application) name
archetypeArtifactId the sort of Maven project you want

What this does...

Creates a project subdirectory and all supporting files as dictated by the default Maven archetype. Anything left blank or unspecified on the command line will be asked for interactively by Maven after you press Enter. There will not be any interaction because everything has been specified here as you will see.

What you see happen...

~/dev $ mvn archetype:generate -DgroupId=com.etretatlogiciels.project \
>                              -DartifactId=project \
>                              -DarchetypeArtifactId=maven-archetype-quickstart \
>                              -DinteractiveMode=false
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.0/maven-archetype-quickstart-1.0.jar
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.0/maven-archetype-quickstart-1.0.jar (5 KB at 18.7 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.0/maven-archetype-quickstart-1.0.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.0/maven-archetype-quickstart-1.0.pom (703 B at 4.8 KB/sec)
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.etretatlogiciels.project
[INFO] Parameter: packageName, Value: com.etretatlogiciels.project
[INFO] Parameter: package, Value: com.etretatlogiciels.project
[INFO] Parameter: artifactId, Value: project
[INFO] Parameter: basedir, Value: /home/russ/dev
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /home/russ/dev/project
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.197s
[INFO] Finished at: Mon Dec 09 15:46:07 MST 2013
[INFO] Final Memory: 15M/148M
[INFO] ------------------------------------------------------------------------
~/dev $ ll
total 12
drwxr-xr-x  3 russ russ 4096 Dec  9 15:46 .
drwxr-xr-x 26 russ russ 4096 Dec  9 15:45 ..
drwxr-xr-x  3 russ russ 4096 Dec  9 15:46 project

What you see created...

~/dev $ tree project
project
+-- pom.xml
`-- src
    +-- main
    |   `-- java
    |       `-- com
    |           `-- etretatlogiciels
    |               `-- project
    |                   `-- App.java
    `-- test
        `-- java
            `-- com
                `-- etretatlogiciels
                    `-- project
                        `-- AppTest.java

11 directories, 3 files

Maven lifecycle phases or targets
  1. validate —validate the project as correct and all necessary information available.
  2. compile —compile the source code of the project.
  3. test —test the compiled source code using a suitable unit testing framework.
  4. package —package the compiled code in its distributable format, such as a JAR, WAR, NAR or even RPM, .deb, etc.
  5. verify —run any checks on results of integration tests to ensure quality criteria are met.
  6. install —install the package in the local repository, for use as a dependency in other projects locally.
  7. deploy —copies the final package to the remote repository for sharing with other projects.

Building the new project
$ mvn package

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ project ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/russ/dev/project/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ project ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/russ/dev/project/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ project ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/russ/dev/project/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ project ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/russ/dev/project/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ project ---
[INFO] Surefire report directory: /home/russ/dev/project/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.etretatlogiciels.project.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ project ---
[INFO] Building jar: /home/russ/dev/project/target/project-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.691s
[INFO] Finished at: Mon Dec 09 15:59:35 MST 2013
[INFO] Final Memory: 14M/209M
[INFO] ------------------------------------------------------------------------

A new subdirectory has been added as a result of the build:

~/dev $ tree project
.
.
.
`-- target
    +-- classes
    |   `-- com
    |       `-- etretatlogiciels
    |           `-- project
    |               `-- App.class
    +-- maven-archiver
    |   `-- pom.properties
    +-- project-1.0-SNAPSHOT.jar
    +-- surefire-reports
    |   +-- com.etretatlogiciels.project.AppTest.txt
    |   `-- TEST-com.etretatlogiciels.project.AppTest.xml
    `-- test-classes
        `-- com
            `-- etretatlogiciels
                `-- project
                    `-- AppTest.class

22 directories, 9 files

Running the newly built project
$ java -cp target/project-1.0-SNAPSHOT.jar com.etretatlogiciels.project.App
Hello World!

Using Maven behind a proxy

If you're trying to use Maven behind a proxy, it won't work because the biggest facet of Maven is to be able to download libraries from an on-line repository. The solution is done in the settings file, ~/.m2/settings.xml. Add the following to this file. Upon a new installation, you'll likely have to create it as it will not already exist.

    <settings>
      <proxies>
       <proxy>
          <active>true</active>
          <protocol>http</protocol>
          <host>web-proxy.austin.hp.com</host>
          <port>8080</port>
          <!--<username>proxyuser</username>
          <password>somepassword</password>
          <nonProxyHosts>www.google.com|*.somewhere.com</nonProxyHosts>-->
        </proxy>
      </proxies>
    </settings>

Re-Maven...

After years of eschewing Maven, I come back to it, did the download, exploded it, added its binary subdirectory to the end of my PATH, fixed working behind a proxy, then did:

~/dev/maven $ mvn archetype:generate

...whereupon, after creating all the stuff in ~/.m2/repository, it asked some things. I had forgot what all this was about. Finally, I took the default as shown, plus decided to write a Hello World project. Then I took the remainder of the defaults:

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 328:
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
Choose a number: 6:
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.1/\
    maven-archetype-quickstart-1.1.jar
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.1/\
    maven-archetype-quickstart-1.1.jar (7 KB at 40.7 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.1/\
    maven-archetype-quickstart-1.1.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.1/\
    maven-archetype-quickstart-1.1.pom (2 KB at 14.9 KB/sec)
Define value for property 'groupId': : com.hp
Define value for property 'artifactId': : helloworld
Define value for property 'version':  1.0-SNAPSHOT: : 1.1
Define value for property 'package':  com.hp: :
Confirm properties configuration:
groupId: com.hp
artifactId: helloworld
version: 1.1
package: com.hp
 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.hp
[INFO] Parameter: packageName, Value: com.hp
[INFO] Parameter: package, Value: com.hp
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: basedir, Value: /home/russ/dev/maven
[INFO] Parameter: version, Value: 1.1
[INFO] project created from Old (1.x) Archetype in dir: /home/russ/dev/maven/helloworld
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1:17.788s
[INFO] Finished at: Mon Nov 18 10:57:06 MST 2013
[INFO] Final Memory: 14M/150M
[INFO] ------------------------------------------------------------------------

What I'm left with in the subdirectory in which I executed this is:

~/dev/maven $ tree
.
`-- helloworld
    +-- pom.xml
    `-- src
        +-- main
        |   `-- java
        |       `-- com
        |           `-- hp
        |               `-- App.java
        `-- test
            `-- java
                `-- com
                    `-- hp
                        `-- AppTest.java

10 directories, 3 files

Building

With the Hello World source code in place, let's just build it into a JAR. The semantics of the Maven command are surprising if you're new. install doesn't (build, then) install software in the sense you might think. What's installed is likely a JAR into your local repository.

~/dev/maven $ mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building helloworld 1.1
[INFO] ------------------------------------------------------------------------
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.6/\
    maven-resources-plugin-2.6.pom
.
.
.
(much churning here)
[INFO] Compiling 1 source file to /home/russ/dev/maven/helloworld/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/russ/dev/maven/helloworld/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ helloworld ---
[INFO] Compiling 1 source file to /home/russ/dev/maven/helloworld/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ helloworld ---
.
.
.
(more churning here)
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.hp.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ helloworld ---
.
.
.
(still more churning)
[INFO] Installing /home/russ/dev/maven/helloworld/target/helloworld-1.1.jar to /home/russ/.m2/repository\
    /com/hp/helloworld/1.1/helloworld-1.1.jar
[INFO] Installing /home/russ/dev/maven/helloworld/pom.xml to /home/russ/.m2/repository/com/hp/helloworld\
    /1.1/helloworld-1.1.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 24.845s
[INFO] Finished at: Mon Nov 18 11:54:40 MST 2013
[INFO] Final Memory: 13M/111M
[INFO] ------------------------------------------------------------------------

Notice that Maven built the project, ran the JUnit test, then build the JAR file. Afterward, this is what we see:

~/dev/maven/helloworld $ ll
total 20
drwxr-xr-x 4 russ russ 4096 Nov 18 11:54 .
drwxr-xr-x 3 russ russ 4096 Nov 18 10:57 ..
-rw-r--r-- 1 russ russ  739 Nov 18 10:57 pom.xml
drwxr-xr-x 4 russ russ 4096 Nov 18 10:57 src
drwxr-xr-x 6 russ russ 4096 Nov 18 11:54 target
~/dev/maven/helloworld $ tree target
target
+-- classes
|   `-- com
|       `-- hp
|           `-- App.class
+-- helloworld-1.1.jar
+-- maven-archiver
|   `-- pom.properties
+-- surefire-reports
|   +-- com.hp.AppTest.txt
|   `-- TEST-com.hp.AppTest.xml
`-- test-classes
    `-- com
        `-- hp
            `-- AppTest.class

What's in the runnable JAR is:

~/dev/maven/helloworld $ $JAVA_HOME/bin/jar -tf ./target/helloworld-1.1.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/hp/
com/hp/App.class
META-INF/maven/
META-INF/maven/com.hp/
META-INF/maven/com.hp/helloworld/
META-INF/maven/com.hp/helloworld/pom.xml
META-INF/maven/com.hp/helloworld/pom.properties

Running it

There's probably a better way, but this is the first way I found that worked and you have to pick the output out of the noise:

~/dev/maven/helloworld $ mvn exec:java -Dexec.mainClass="com.hp.App"
[INFO] Scanning for projects...
Downloading: http://repo.maven.apache.org/maven2/org/codehaus/mojo/exec-maven-plugin/maven-metadata.xml
~/dev/maven/helloworld $ mvn exec:java -Dexec.mainClass="com.hp.App"
.
.
.
(much churning here)
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building helloworld 1.1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) @ helloworld >>>
[INFO]
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) @ helloworld <<<
[INFO]
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ helloworld ---
.
.
.
(more churning here)
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.927s
[INFO] Finished at: Mon Nov 18 12:16:57 MST 2013
[INFO] Final Memory: 9M/148M
[INFO] ------------------------------------------------------------------------

Saving the whales...

One thing that I loathed about Java back in the 90s when it started was the extra-deep subdirectory structure. Whoever came up with Maven was enamored with it. Whoever created Eclipse did the world a favor by tossing the unnecessary bits. Here's how to modify pom.xml so that the extra depth isn't there.

We start out with a sample project, default style:

~/dev/maven $ tree
.
`-- helloworld
    +-- pom.xml
    `-- src
        +-- main
        |   `-- java
        |       `-- com
        |           `-- hp
        |               `-- App.java
        `-- test
            `-- java
                `-- com
                    `-- hp
                        `-- AppTest.java

10 directories, 3 files

Then, we fix it to look like it was created by Eclipse instead:

~/dev/maven $ tree
.
`-- helloworld
    +-- pom.xml
    +-- src
    |   `-- com
    |       `-- hp
    |           `-- App.java
    `-- test
        `-- com
            `-- hp
                `-- AppTest.java

10 directories, 3 files

To accomplish this, we need only place the following into pom.xml:

  <build>
    <sourceDirectory>${project.basedir}/src</sourceDirectory>
    <testSourceDirectory>${project.basedir}/test</testSourceDirectory>
    <resources>
      <resource>
        <directory>${project.basedir}/src</directory>
      </resource>
    </resources>
  </build>

This works because Maven stuff is inerited and properties can be overridden. These settings are rewrites of the same ones, but only those we wish to overwrite, in the Super POM. This is from the Super POM according to POM Reference: The Basics: Inheritance: The Super POM:

  <build>
    <directory>${project.basedir}/target</directory>
    <outputDirectory>${project.build.directory}/classes</outputDirectory>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
    <scriptSourceDirectory>src/main/scripts</scriptSourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>${project.basedir}/src/test/resources</directory>
      </testResource>
    </testResources>
    .
    .
    .
  </build>

Creating my own archetype...

This is done by creating an actual, if uncoded, project. I'm following Guide to Creating Archetypes.

Step 1: Generate the project

~/dev/maven/archetype $ mvn archetype:generate \
                            -DgroupId=com.etretatlogiciels \
                            -DartifactId=java-project \
                            -DarchetypeArtifactId=maven-archetype-archetype
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-archetype\
    /1.0/maven-archetype-archetype-1.0.jar
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-archetype\
    /1.0/maven-archetype-archetype-1.0.jar (7 KB at 25.0 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-archetype\
    /1.0/maven-archetype-archetype-1.0.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-archetype\
    /1.0/maven-archetype-archetype-1.0.pom (528 B at 3.1 KB/sec)
[INFO] Using property: groupId = com.etretatlogiciels
[INFO] Using property: artifactId = java-project
Define value for property 'version':  1.0-SNAPSHOT: : 1.0
[INFO] Using property: package = com.etretatlogiciels
Confirm properties configuration:
groupId: com.etretatlogiciels
artifactId: java-project
version: 1.0
package: com.etretatlogiciels
 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-archetype:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.etretatlogiciels
[INFO] Parameter: packageName, Value: com.etretatlogiciels
[INFO] Parameter: package, Value: com.etretatlogiciels
[INFO] Parameter: artifactId, Value: java-project
[INFO] Parameter: basedir, Value: /home/russ/dev/maven/archetype
[INFO] Parameter: version, Value: 1.0
[INFO] project created from Old (1.x) Archetype in dir: /home/russ/dev/maven/archetype/java-project
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.900s
[INFO] Finished at: Mon Nov 18 15:09:24 MST 2013
[INFO] Final Memory: 15M/209M
[INFO] ------------------------------------------------------------------------

Step 2: Add the archetype.xml file

I found that java-project/src/main/resources/META-INF/maven/archetype.xml was almost what I wanted. I modified it like this:

<archetype>
  <id>java-project</id>
  <allowPartial>true</allowPartial>
  <sources>
    <source>src/App.java</source>
  </sources>
  <resources>
    <resource>src</resource>
  </resources>
  <testSources>
    <source>test/AppTest.java</source>
  </testSources>
  <testResources>
    <resource>test</resource>
  </testResources>
</archetype>

Step 3: Doctor the pom file

Actually, this is the "prototype" pom.xml spoken of in the article. They don't actually say that the pom.xml file generated is the one to modify, they only speak of "the prototype files and the prototype pom.xml".

The point is that you need to "soften" the generated pom.xml to account for how consumers of the archetype want to name their project, name their package, etc. So, my pom.xml started out this way:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.etretatlogiciels</groupId>
  <artifactId>java-project</artifactId>
  <version>1.0</version>
  <name>Archetype - java-project</name>
  <url>http://maven.apache.org</url>
</project>

...and I modified (softened) it like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>${groupId}</groupId>
  <artifactId>${artifactId}</artifactId>
  <version>${version}</version>
  <name>Archetype - java-project</name>
  <url>http://maven.apache.org</url>
</project>

Prototype files

Not part of this note: the ability to create actual Java source code with templates (soft stuff) inside that Maven will use to generate code. For example, App.java needs a soft package inside since it shouldn't be my own (com.etretatlogiciels).

Step 4: Build and install the new archetype

This puts it into the target subdirectory.

~/dev/maven/java-project $ mvn install

...with this appearance:

~/dev/maven/archetype/java-project $ tree
.
+-- pom.xml
+-- src
|   `-- main
|       `-- resources
|           +-- archetype-resources
|           |   +-- pom.xml
|           |   `-- src
|           |       +-- main
|           |       |   `-- java
|           |       |       `-- App.java
|           |       `-- test
|           |           `-- java
|           |               `-- AppTest.java
|           `-- META-INF
|               `-- maven
|                   `-- archetype.xml
`-- target
    +-- classes
    |   +-- archetype-resources
    |   |   +-- pom.xml
    |   |   `-- src
    |   |       +-- main
    |   |       |   `-- java
    |   |       |       `-- App.java
    |   |       `-- test
    |   |           `-- java
    |   |               `-- AppTest.java
    |   `-- META-INF
    |       `-- maven
    |           `-- archetype.xml
    +-- java-project-1.0.jar
    `-- maven-archiver
        `-- pom.properties

22 directories, 11 files

Please note that this "install" action causes the new archetype to be copied into Maven. That's how it's able to be used in the next step.

~/dev/maven/archetype/java-project $ locate java-project
/home/russ/.m2/repository/com/etretatlogiciels/java-project
/home/russ/.m2/repository/com/etretatlogiciels/java-project/1.0
/home/russ/.m2/repository/com/etretatlogiciels/java-project/maven-metadata-local.xml
/home/russ/.m2/repository/com/etretatlogiciels/java-project/1.0/_remote.repositories
/home/russ/.m2/repository/com/etretatlogiciels/java-project/1.0/java-project-1.0.jar
/home/russ/.m2/repository/com/etretatlogiciels/java-project/1.0/java-project-1.0.pom

Step 4: Use the new archetype

...to try it out. After a warning I got, I had to specify archetypeRepository so Maven could find it.

$ mvn archetype:generate                          \
  -DarchetypeRepository=/home/russ/.m2/repository \
  -DarchetypeGroupId=com.etretatlogiciels         \
  -DarchetypeArtifactId=java-project              \
  -DarchetypeVersion=1.0                          \
  -DgroupId=some new package path                 \
  -DartifactId=some new project name

If you got this far, you discovered that the developers of Maven were incapable of understanding that anyone would ever want to modify their precious subdirectory layout despite plent of protest to the contrary.

src
  +-- main
  |   `-- java
  `-- test
      `-- java

You simply cannot create an archetype that will modify this basic layout to match, for example, the simple layout of Eclipse's Java and WTP projects. This is why when you use Maven and Eclipse, the latter's simple layout goes out the window.

What you can do, however, especially in Eclipse and IntelliJ IDEA, is just present Maven with the structure you want to use, which is reflected slightly in pom.xml, and it will just use it without imposing the usual structure. The best advice can be found in Converting a newly set-up or existing project to Maven in Eclipse near the bottom of this page.


Maven 3 and versions of dependencies

In Maven 2, if you didn't specify the version for plug-ins and dependencies used in pom.xml, it would pick the latest plugin version automatically. With Maven 3, you get errors like:

[WARNING] Some problems were encountered while building the effective model

[ERROR] Some problems were encountered while processing the POMs 'dependencies.dependency.version' for \
    package-path:jar is missing @ line number, column column number

To fix this, you have to specify the plug-in or JAR version.


Maven and "scope"

One example, for now:

<scope>import</scope>

...indicates something in dependency management whose pom.xml is imported for use usually in setting the versions of many or all the JARs pulled into this project. It's instead of explicitly maintaining versions of JAR in the project's pom.xml when they must always be the same as in the JAR/project referenced by this scope.


"Shaded" JARs

Also known as "uber JARs," is when one JAR is used to subsume a bunch of other JARs, mitigating interdependence, for convenience. This is Maven terminology.


JAVA_HOME and Maven...

JAVA_HOME should be established in .profile and not use the tilde character in its definition:

export JAVA7_HOME=/home/russ/dev/jdk1.7.0_51
export JAVA6_HOME=/home/russ/dev/jdk1.6.0_45
export  JAVA_HOME=${JAVA7_HOME}
export   IDEA_JDK=${JAVA7_HOME}
export    M2_HOME=/home/russ/dev/apache-maven-3.1.1
export         M2=${M2_HOME}/bin

PATH=${JAVA_HOME}/bin:${PATH}
PATH=${PATH}:${M2}

Set Maven up as shown, too.

~ $ java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)
~ $ mvn --version
Apache Maven 3.1.1 (0728685237757ffbf44136acec0402957f723d9a; 2013-09-17 09:22:22-0600)
Maven home: /home/russ/dev/apache-maven-3.1.1
Java version: 1.7.0_51, vendor: Oracle Corporation
Java home: /home/russ/dev/jdk1.7.0_51/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.2.0-23-generic", arch: "amd64", family: "unix"

Useful Maven commands...

To equip the subdirectory with Eclipse project files (.project, .classpath and .settings):

$ mvn eclipse:eclipse

Note that, if you confirm that a dependency is a) listed in pom.xml and you can also b) see it resident in .m2/repository, but c) Eclipse still gives missing class errors it should find inside that JAR, you likely can also reissue the previous command line to help Eclipse get over it.

(See more on this command elsewhere on this page.)

Build project without running tests. It does compile the test code, it just doesn't run it:

$ mvn -DskipTests

Here's how to force Maven to update its repository for one project (or, as here, a group of projects subordinate to a project):

$ mvn dependency:resolve
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Work-Streams-ACME
[INFO] Project configuration data
[INFO] acme-core
[INFO] acme-utils
[INFO] acme-service
[INFO] acme-web-service
[INFO] acme-api-client
[INFO] acme-person-client
[INFO] acme-client
[INFO] acme-test-fixture
[INFO] acme-service-tests
[INFO] acme-write
[INFO] acme-probe
[INFO]
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Work-Streams-ACME 1.6.38-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.3:resolve (default-cli) @ acme-parent ---
[INFO]
[INFO] The following files have been resolved:
[INFO]    none
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Project configuration data 1.6.38-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.3:resolve (default-cli) @ acme-config ---
[INFO]
[INFO] The following files have been resolved:
[INFO]    com.acme.common:fs-initd:zip:3.0.b84:compile
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building cmisx-core 1.6.38-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.3:resolve (default-cli) @ acme-core ---
[INFO]
[INFO] The following files have been resolved:
[INFO]    com.acme.common:work-jvm-protector:jar:1.3.b5:compile
[INFO]    com.acme.common:work-async-service:jar:1.3.b5:compile
...
             [INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] Work-Streams-ACME ................................ SUCCESS [1.434s]
[INFO] Project configuration data ....................... SUCCESS [0.039s]
[INFO] acme-core ........................................ SUCCESS [0.183s]
[INFO] acme-utils ....................................... SUCCESS [0.094s]
[INFO] acme-service ..................................... SUCCESS [0.635s]
[INFO] acme-web-service ................................. SUCCESS [0.342s]
[INFO] acme-api-client .................................. SUCCESS [0.008s]
[INFO] acme-person-client ............................... SUCCESS [0.162s]
[INFO] acme-client ...................................... SUCCESS [0.126s]
[INFO] acme-test-fixture ................................ SUCCESS [0.137s]
[INFO] acme-service-tests ............................... SUCCESS [0.166s]
[INFO] acme-write ....................................... SUCCESS [0.175s]
[INFO] acme-probe ....................................... SUCCESS [0.179s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.885s
[INFO] Finished at: Thu Feb 13 14:08:13 MST 2014
[INFO] Final Memory: 20M/174M
[INFO] ------------------------------------------------------------------------

When experiencing inexplicable Maven errors...

When experiencing an inexplicable error running a Maven script, don't fail to think about pom.xml files in directories higher up from which the erroring one may be inheriting definitions (and problems). One of the them may have inadvertantly been mucked by a stray editor action.


Hierarchical pom.xml files

If you've got a superior (higher up) pom.xml, put the version into that and the dependency (into dependencyManagement with version, but the dependency into the subordinate pom.xml without the version. The parent pom.xml should govern the version; the child pom.xmls just eat whatever the parent says they must eat.


Interdependence and hierarchy

If module AB in project A needed to depend on dependency C for something that consumer D should be able to depend on by virtue of depending on project A for (that and many other) things, the dependency needs to be expressed in module AB's pom.xml, but dependency C need not be expressed in consumer D's pom.xml.


Maven dependencies

First, grab all dependencies:

$ mvn dependency:resolve

Then, list them with -o to keep noise low, remove extra information and duplicates:

$ mvn -o dependency:list \
        | grep ":.*:.*:.*" \
        | cut -d] -f2- \
        | sed 's/:[a-z]*$//g' \
        | sort -u

Dependency and dependency management

In pom.xml there are dependencies and mere dependency management. This...

<dependencyManagement>
  <dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk</artifactId>
    <version>1.7.11</version>
  </dependency>
</dependencyManagement>

...means, "If the Amazon Java SDK is noted as a dependency, then use this version." It does not mean, "Make the Amazon Java SDK 1.7.11 a dependency."

In IntelliJ, the module pom.xml must express the dependency if it is to exist:

<dependency>
  <groupId>com.amazonaws</groupId>
  <artifactId>aws-java-sdk</artifactId>
</dependency>

The version expressed in the root's dependencyManagement is the version that will be used. Typically, the version is the object of a property, consumed in the dependencyManagement section, but not referred to (nor even to the property) in the dependency section.


List of Maven build targets

There are also separate clean and site lifecycle targets that are different from these.

validate validate the project is correct and all necessary information is available.
compile compile the source code of the project.
test test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed.
package take the compiled code and package it in its distributable format, such as a JAR.
integration-test process and deploy the package if necessary into an environment where integration tests can be run.
verify run any checks to verify the package is valid and meets quality criteria.
install install the package into the local repository, for use as a dependency in other projects locally.
deploy done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.
dependency:list mvn -o dependency:list will list out the (JAR) dependencies in your project, module-by-module if you have submodules in it.

Maven's eclipse:eclipse

To convert an existing Eclipse Java project over to use Maven, do this.

Start out with a pom.xml, you must write this yourself to prime the pump.

$ mvn eclipse:eclipse

This created .classpath and .project, but no subdirectory structure. I sort of expected something based on groupId.

After importing the project into Eclipse and creating new source folders src and test, I got:

russ@nargothrond:~/dev/jtx$ ll
total 32
drwxr-xr-x.  5 russ russ 4096 Nov 26 14:15 .
drwxrwxr-x. 13 russ russ 4096 Nov 26 14:03 ..
-rw-r--r--.  1 russ russ 2332 Nov 26 14:15 .classpath
-rw-r--r--.  1 russ russ  821 Nov 26 13:53 pom.xml
-rw-r--r--.  1 russ russ  439 Nov 26 14:04 .project
drwxrwxr-x.  2 russ russ 4096 Nov 26 14:15 src
drwxrwxr-x.  3 russ russ 4096 Nov 26 14:15 target
drwxrwxr-x.  2 russ russ 4096 Nov 26 14:15 test

After creating packages in src and test:

russ@nargothrond:~/dev/jtx$ tree
.
+-- pom.xml
+-- src
|   `-- com
|       `-- acme
|           `-- jtx
+-- target
|   `-- classes
|       `-- com
|           `-- acme
|               `-- jtx
+-- test
    `-- com
        `-- acme
            `-- jtx

13 directories, 1 file

Doing mvn eclipse:eclipse, then importing a project into the Eclipse workspace isn't quite enough.

Right-click project and choose Configure → Convert to Maven Project. This option will not exist (contextually) if it's already a Maven project.

Once that's done, right-click the project again and choose Maven → Download JavaDoc (sic). Indeed, after doing that, comparing .m2/repository contents with what they used to be will reveal that the Javadoc did indeed come down.

However, waving the mouse over symbols, control-clicking a symbol, etc. still will not appear helpful, but ContentAssist will begin to work. To convince yourself, type a variable name, followed by a dot (.) and then press Ctrl + Spacebar.

(Thanks, Scott!)

Now, this is what Build Path looks like after mvn eclipse:eclipse. You must construct pom.xml right, which Configure → Convert to Maven Project will screw up, so keep a copy of what you wanted because you'll need to edit the result. I found, in particular, that the dependencies had been smoked.

On occasion (still haven't figured out what happens or how to fix it), right-clicking the project will not offer a Maven item (for use in choosing Download JavaDoc).

What I found is that I had to do Configure → Convert to Maven Project a second time. Then the Maven item appeared. Also, my dependencies did not disappear when I did it the second time.

Here are the differences with the project before I added Maven to it. Also, I went on to solve a number of other problems arising.

.classpath:
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-    <classpathentry kind="src" path="src"/>
-    <classpathentry kind="src" path="test"/>
-    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
-    <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-    <classpathentry kind="lib" path="lib/log4j-api-2.1.jar" sourcepath="/home/russ/dev/json-to-xml/lib/log4j-api-2.1-sources.jar">
+    <classpathentry kind="var" path="M2_REPO/org/apache/logging/log4j/log4j-api/2.1/log4j-api-2.1.jar"/>
+    <classpathentry kind="var" path="M2_REPO/org/apache/logging/log4j/log4j-core/2.1/log4j-core-2.1.jar"/>
+    <classpathentry kind="var" path="M2_REPO/junit/junit/4.11/junit-4.11.jar">
         <attributes>
-            <attribute name="javadoc_location" value="jar:platform:/resource/json-to-xml/lib/log4j-api-2.1-javadoc.jar!/"/>
+            <attribute name="javadoc_location" value="jar:file:/home/russ/.m2/repository/junit/junit/4.11/junit-4.11-javadoc.jar!/"/>
         </attributes>
     </classpathentry>
-    <classpathentry kind="lib" path="lib/log4j-core-2.1.jar" sourcepath="lib/log4j-core-2.1-sources.jar">
+    <classpathentry kind="var" path="M2_REPO/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar">
         <attributes>
-            <attribute name="javadoc_location" value="jar:platform:/resource/json-to-xml/lib/log4j-core-2.1-javadoc.jar!/"/>
+            <attribute name="javadoc_location" value="jar:file:/home/russ/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-javadoc.jar!/"/>
         </attributes>
     </classpathentry>
-    <classpathentry kind="output" path="bin"/>
+    <classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
+        <attributes>
+            <attribute name="optional" value="true"/>
+            <attribute name="maven.pomderived" value="true"/>
+        </attributes>
+    </classpathentry>
+    <classpathentry kind="src" output="target/test-classes" path="test">
+        <attributes>
+            <attribute name="optional" value="true"/>
+            <attribute name="maven.pomderived" value="true"/>
+        </attributes>
+    </classpathentry>
+    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+        <attributes>
+            <attribute name="maven.pomderived" value="true"/>
+        </attributes>
+    </classpathentry>
+    <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+        <attributes>
+            <attribute name="maven.pomderived" value="true"/>
+        </attributes>
+    </classpathentry>
+    <classpathentry kind="output" path="target/classes"/>
 </classpath>
.project:
 <?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
        <name>json-to-xml</name>
-       <comment></comment>
+       <comment>NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are
not supported in M2Eclipse.</comment>
        <projects>
        </projects>
        <buildSpec>
@@ -10,8 +10,14 @@
                        <arguments>
                        </arguments>
                </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
        </buildSpec>
        <natures>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
                <nature>org.eclipse.jdt.core.javanature</nature>
        </natures>
 </projectDescription>
.settings/org.eclipse.jdt.core.prefs:
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
.settings/org.eclipse.m2e.core.prefs:

(new file)

activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
pom.xml:

(This is my new one.)

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme</groupId>
  <artifactId>json-to-xml</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <name>json-to-xml</name>
  <description>JSON-to-XML filter not requiring a POJO description of the JSON.</description>

  <properties>
    <log4j.version>2.1</log4j.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <build>
    <sourceDirectory>src</sourceDirectory>
    <testSourceDirectory>test</testSourceDirectory>
    <resources>
      <resource>
        <directory>src</directory>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.1</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>
target:

(new subdirectory because Maven doesn't like bin)

.gitignore:

(ibid)

-bin/
+target/
 *.swp

Other stuff:

[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!

Add:

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

If you see:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
(default-compile) on project json-to-xml: Fatal error compiling: invalid target release: 1.8 → [Help 1]
Do this:
~ $ cat .mavenrc
JAVA_HOME=/home/russ/dev/jdk1.8.0_25

Using or not Maven: Apache HTTP as a practical example

(This is advice I once gave to someone in the Eclipse Newcomers' forum.)

A wee bit new to Eclipse? You need to get the JARs from Apache ( https://hc.apache.org/downloads.cgi. How this is done depends on how you build.

Method 1, the hard way

You're not using Maven or Ivy.
You download either the binary or the source.

If new to Eclipse, you should at least do this once so you sort of understand how JARs work directly in the project.

Disadvantages

Advantages

Unless you want to look at the source code or think Apache's got a bug you'll have to trace through, download the binary.

  1. Create a subdirectory, lib, in your project.
  2. Use Build Path → Libraries → Add JARs to add:
    • httpclient-4.3.6.jar
    • httpclient-cache-4.3.6.jar
    • httpcore-4.3.3.jar
    • httpmime-4.3.6.jar
  3. You'll also need:
    • commons-codec-1.6.jar
    • commons-logging-1.1.3.jar
    • fluent-hc-4.3.6.jar

As soon as you do the Build Path step, this will make Apache HTTP interface available to Eclipse Content Assist and you can use the errors flagged by Eclipse in your source code to get the imports added (Eclipse will do this for you).

Method 2, the easy, modern way

Use Maven, Ivy, Gradle, etc. (Maven shown here.) You must add Apache HTTP Components to the <dependencies> section in your pom.xml file:

  1. Go to http://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient/4.3.6 and look at the XML to copy and paste under the Maven tab:
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.3.6</version>
    </dependency>
    

Disadvantages

Advantages

If you're not using Maven or Ivy, you should make a mental note to learn. The curve's a little steep and confusing at first, but Maven, as much as I may not like it personally, is the lingua franca in our industry today.


A brief exposé on Maven dependency

(Yeah, I know, this is pretty random.)

In the <dependencyManagement> section it's possible to specific a "super" project whose pom.xml will be used to specific dependencies. This will suffice also for subdirectory pom.xmlfiles. In our exercise here, we're replacing just such a super project, fs-dependency-management with another, tem-temple.

Any project that's got an integration.properties file is a "top-level" project and is a candidate, like tem-temple, for becoming a "super" project. Whether chosen so or not, it falls outside most of this discussion in that such a project must not make use of dependency management to name another project, like fs-dependency-management, tem-temple or other, from which to take its dependencies and versions.

For projects that aren't top-level...

Anything that's not in the "super" project's pom.xml will need to be listed in the project pom.xml with its version because not obtainable from the "super" project's pom.xml. Versions are ideally specified in the project's "parent" pom.xml' <properties> section; child pom.xml files will get them from there, they do not need repeating in sibling pom.xml files, etc.

In subordinate pom.xml files, use the <dependencies> section, and not the <dependencyManagement> section. The group- and artifactId need listing; use the macro definition for the version:

<dependency>
  <groupId>com.acme.fireworks</groupId>
  <artifactId>jato-assist-rocket</artifactId>
  <version>${jato-assist-rocket.version}</version>
</dependency>

However, any dependency already specified in the parent (wait for it) or super project need not have its version specified.

Dependency specification in super- and parent projects

Specifying dependencies this way will ensure that all adopting projects, whether by dependency management or by hierarchical inheritance, are enforced in the child pom.xml.

In the super project, the dependency is specified using the standard <dependencies> section including version, of course.

In the simple parent pom.xml file, this can be done two ways.

  1. A dependency can be set up in <dependencyManagement> with version. This means that, "if any child pom.xml specifies a need for a that dependency, it will get the same one (i.e.: same version) specified in the parent.
  2. A dependency can be defined outside the <dependencyManagement> section. If a parent does this, it is available via inheritance to the child pom.xml (though the child may override, but this is not recommended).


Deploying artifacts from the deploy Maven lifecycle

See these notes.


AntBuildException
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-antrun-plugin:1.7:run (blah-xml) \
Jon project blah: An Ant BuildException has occured: Execute failed: java.io.IOException: Cannot run \
program "xmllint" (in directory "/home/russ/acme-projects/blah/target/sources"): error=2, No such file or directory
[ERROR] around Ant part ...... @ 18:120 in /home/russ/acme-projects/blah/target/antrun/build-main.xml

xmllint is missing. Install it:

$ sudo bash
# apt-get update
# apt-get install libxml2-utils

Converting a newly set up or existing project to Maven in Eclipse

I don't use Maven archetypes as I've long been used to doing things Eclipse's way and I like the organization and the decrease in subdirectory depth anyway. Here's how I convert a project to using Maven.

I had to install Maven (~/dev/apache-maven-3.3.3) and set up environment variables M2_HOME, M2 and JAVA_HOME as I believe is recounted at the top of this page.

What did I do to promote the project to a Maven build?

  1. I copied over the pom.xml of another, unrelated project as a starting point. I changed all its features to reflect the new project and set up the dependencies that you can find noted whenever you try to grab a library, read a tutorial, etc. Google around for the JAR you're after and you'll probably find a suitable dependency statement to put into your pom.xml.

  2. I right-clicked the project and chose Configure → Convert to Maven Project....

  3. I opened a shell and went to the project root, because I'm more of a command-line guy, and did mvn test compile. I could have right-clicked on the project, chosen Maven → Update Project... (I have tried this; it does work).

  4. I kept running the command-line compile and also Eclipse Project → Clean, and fixing pom.xml (mostly adding the structures JARs) until I got a clean build.

Decidedly, I've been shamed into becoming a Maven guy.


How Maven inheritance works (and a note on aggregation)

The parent project pom.xml file can express that its project has a specific packaging, namely, pom (see below). Property definitions, dependencies, plug-ins, repositories and other resources, except those the subordinate wishes to make different from the parent, are inherited.

Basically, this is how to proceed. The parent pom.xml is sketched out with all the properties, repositories (if explicit specifications to make), dependencies, plug-ins, etc.

<xml version="1.0">
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM.4000
                      http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.etretatlogiciels</groupId>
  <artifactId>some-code</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <organization>
    <name>Etretat Logiciels, DBA</name>
    <url>http://www.etretatlogiciels.com</url>
  </organization>

  ...

  <properties>
    <junit-version>4.11<junit-version>
    <surefire-version>2.15<surefire-version>
  <properties>

  <dependencies>
    <dependency>
      <groupId>junit<groupId>
      <artifactId>junit<artifactId>
      <version>${junit-version}<version>
      <scope>test<scope>
    <dependency>
  <dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins<groupId>
          <artifactId>maven-surefire-plugin<artifactId>
        <version>${surefire-version}<version>
      <plugin>
    <plugins>
    <pluginManagement>
  <build>
</project>

...and its child pom.xml is templated thus:

<xml version="1.0">
<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM.4000
                      http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.etretatlogiciels</groupId>
    <artifactId>some-code</artifactId>
    <version>1.0.0</version>
    <relativePath>../pom.xml</relativePath>
  <parent>

  <artifactId>api</artifactId>
  <packaging>jar</packaging>
  <name>module</name>
</project>

<pluginManagement />

The purpose of <pluginManagement /> is to configure project builds that inherit from the pom.xml that defines <pluginManagement />. What's inside <pluginManagement /> doesn't affect the the project build (i.e.: the root pom.xml), but the behavior of the subordinate pom.xml files.

Aggregation...

...is when you use <modules><module>...</module></modules> to list the subordinate components of a project. It's not well explained how this differs from hierarchy and inheritance still applies, and there's also a way to share resources between subordinate pom.xml, but you didn't hear it from me.


How Maven repositories work

Downloading during Maven build...

  1. By default, you download JARs from the central repository.
  2. To override this, you can specify a mirror either by a statement in the individual pom.xml or a setting in ~/.m2/settings.xml.
    <settings>
    ...
      <mirrors>
        <mirror>
          <id>       UK                          </id>
          <name>     UK Central                  </name>
          <url>      http://uk.maven.org/maven2  </url>
          <mirroOf>  central                     </mirroOf>
      </mirror>
    </mirrors>
    ...
    </settings>
    
  3. You can download from an internal repository doing this (in pom.xml):
    <project>
      ...
      <repositories>
        <repository>
          <id>   local-maven-repository  </id>
          <url>  http://maven.local      </url>
        </repository>
      </repositories>
      ...
    </project>
    
  4. You can force Mave to use a single, in-house repository mirroring all requests and containing all of the desired artifacts.
    <settings>
    ...
      <mirrors>
        <mirror>
          <id>       internal-repository                       </id>
          <name>     Maven repository manager running locally  </name>
          <url>      http://maven.local                        </url>
          <mirroOf>  *                                         </mirroOf>
      </mirror>
    </mirrors>
    ...
    </settings>
    

Uploading...

  1. You must have privileges to upload.
  2. You need access via SCP, SFTP, FTP, WebDAV or the filesystem using a wagon, or plug-in, in Maven.
  3. In setting up an internal repository, you need only follow the layout already in use at other, remote sites. For example, ...
    maven2
    +-- repository1
    +-- repository2
    `-- repositoryN
        `-- software-name
            +-- name1
            +-- name2
            `-- nameN
                +-- version1
                +-- version2
                `-- versionN
                    +-- name-versionN.jar
                    +-- name-versionN.jar.md5
                    +-- name-versionN.jar.sha1
                    +-- name-versionN.javadoc.jar
                    +-- name-versionN.javadoc.jar.md5
                    +-- name-versionN.javadoc.jar.sha1
                    +-- name-versionN.pom
                    +-- name-versionN.pom.md5
                    +-- name-versionN.pom.sha1
                    +-- name-versionN.sources.jar
                    +-- name-versionN.sources.jar
                    +-- name-versionN.sources.jar
                    +-- maven-metadata.xml
                    +-- maven-metadata.xml.md5
                    `-- maven-metadata.xml.sha1
    

    The best way to do this is to build a project, then copy the downloaded JARs to the site demonstrated just above.


Local-to-project Maven repository

Let's say I'm working on a project named project. (I have to lay this out yet again because I can't find that I doc'd it anywhere.)

Please note the following, local place to put a JAR, cda2fhir-0.2.jar, that is not in any Maven central repository, so that it gets built in my project:

~/dev/project $ tree lib
lib
└── tr
    └── com
        └── srcdc
            └── cda2fhir
                ├── 0.2
                │   ├── cda2fhir-0.2.jar
                │   ├── cda2fhir-0.2.jar.md5
                │   ├── cda2fhir-0.2.jar.sha1
                │   ├── cda2fhir-0.2.pom
                │   ├── cda2fhir-0.2.pom.md5
                │   └── cda2fhir-0.2.pom.sha1
                ├── maven-metadata.xml
                ├── maven-metadata.xml.md5
                └── maven-metadata.xml.sha1
5 directories, 9 files

...and here are the entries in pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                   http://maven.apache.org/xsd/maven-4.0.0.xsd">
  .
  .
  .
  <properties>
    <cda2fhir.version>0.2</cda2fhir.version>
  </properties>
  .
  .
  .
  <repositories>
    <repository>
      <id>data-local</id>
      <name>data</name>
      <-- Hey, this next line is the same as:
            "file:///home/russ/dev/project/lib"
          -->
      <url>file://${project.basedir}/lib</url>
    </repository>
    .
    .
    .
  </repositories>
  .
  .
  .
  <dependencies>
    <dependency>
      <groupId>tr.com.srcdc</groupId>
      <artifactId>cda2fhir</artifactId>
      <version>${cda2fhir.version}</version>
    </dependency>
    .
    .
    .
  <dependencies>

A bigger study...

It looks like I'm going to have to create a private library subdirectory à la Maven because the latest of the MDHT (and Eclipse) JARs aren't available via Maven repositories, not even the http://devsoap.sitenv.org:8081/artifactory. I attempted to e-mail [email protected] for help or advice on this artifactory, but that e-mail address no longer exists.

So, I have little choice but to attempt to proceed. The biggest challenge will be to find pom.xml for each of these JARs. See Local-to-project Maven repository for how to do this. It's going to be very tedious. The JARs in question are listed here. I'm checking the dates of the JARs to determine which can still be got via Maven. The JARs listed here purport to be the latest and, for the most part, they are later than what I've been getting via Maven. The ones I mark with will be got via existing Maven means; the rest will have to be set up. Those marked with do not appear to be used so far despite their presence in this list.

russ@nargothrond ~/Downloads/mdht/mdhtruntime $ tree
.
├── mdht
│   ├── org.eclipse.mdht.emf.runtime-3.0.0.201802220601.jar
│   ├── org.eclipse.mdht.uml.cda-3.0.0.201802220601.jar
│   ├── org.eclipse.mdht.uml.hl7.datatypes-3.0.0.201802220601.jar
│   ├── org.eclipse.mdht.uml.hl7.rim-3.0.0.201802220601.jar
│   ├── org.eclipse.mdht.uml.hl7.vocab-3.0.0.201802220601.jar
│   ├── ✗ org.hl7.cbcc.privacy.consentdirective_1.0.0.20170920.jar
│   ├── ✗ org.hl7.security.ds4p.contentprofile_3.0.0.20170920.jar
│   ├── org.openhealthtools.mdht.uml.cda.consol2-3.0.3.20180222.jar
│   └── org.openhealthtools.mdht.uml.cda.mu2consol-3.0.3.20180222.jar
└── non-mdht
    ├── ✓ lpg.runtime.java-2.0.17.v201004271640.jar
    ├── org.eclipse.emf.common-2.12.0.v20160420-0247.jar
    ├── org.eclipse.emf.ecore-2.12.0.v20160420-0247.jar
    ├── org.eclipse.emf.ecore.xmi-2.12.0.v20160420-0247.jar
    ├── org.eclipse.ocl-3.6.0.v20160523-1914.jar
    ├── org.eclipse.ocl.common-1.4.0.v20160521-2033.jar
    ├── org.eclipse.ocl.ecore-3.6.0.v20160523-1914.jar
    ├── org.eclipse.uml2.common-2.1.0.v20170227-0935.jar
    └── org.eclipse.uml2.types-2.0.0.v20170227-0935.jar

Here's what the pom.xml file looks like. Unless I can find another way, I'll have to generate these by hand for all of the above.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.eclipse.emf.common</groupId>
  <artifactId>org.eclipse.emf.common</artifactId>
  <version>2.11.1.v20160208-0816</version>
  <description>Artifactory auto generated POM</description>
</project>

I have seen scripts to do this, however. Here's one to try:

#!/bin/sh
if [ -n "$*" ]; then
  mvn -e install:install-file \
       -DlocalRepositoryPath=$1 \
       -DcreateChecksum=true \
       -Dpackaging=jar \
       -Dfile=$2 \
       -DgroupId=$3 \
       -DartifactId=$4 \
       -Dversion=$5 \
       -Dpackaging=jar \
       -DgeneratePom=true
else
  echo "Create local artifactory repository, arguments:"
  echo "  1: path to repository subdirectory"
  echo "  2: path to JAR"
  echo "  3: groupId"
  echo "  4: artifactId"
  echo "  5: version"
fi

...where (most of this comes from pom.xml dependencies):

(The suggestion comes from https://stackoverflow.com/questions/2018965/maven-installing-artifacts-to-a-local-repository-in-workspace.) Let's run this and see:

russ@nargothrond ~/Downloads/mdht/mdhtruntime/non-mdht $ ../make-repo.sh \
    ../lib \
    ./org.eclipse.uml2.types-2.0.0.v20170227-0935.jar \
    org.eclipse.uml2.types \
    org.eclipse.uml2.types \
    2.0.0.v20170227-0935
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install-file (default-cli) @ standalone-pom ---
[INFO] pom.xml not found in org.eclipse.uml2.types-2.0.0.v20170227-0935.jar
[INFO] Installing /home/russ/Downloads/mdht/mdhtruntime/non-mdht/org.eclipse.uml2.types-2.0.0.v20170227-0935.jar \
            to /home/russ/Dow...
[INFO] Installing /tmp/mvninstall614828569340065552.pom to /home/russ/Downloads/mdht/mdhtruntime/lib/org/ecl...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.289 s
[INFO] Finished at: 2018-06-01T11:26:41-06:00
[INFO] Final Memory: 8M/481M
[INFO] ------------------------------------------------------------------------

...and I see this:

russ@nargothrond ~/Downloads/mdht/mdhtruntime $ tree lib
lib
└── org
    └── eclipse
        └── uml2
            └── types
                └──org.eclipse.uml2.types
                   ├── 2.0.0.v20170227-0935
                   │   ├── org.eclipse.uml2.types-2.0.0.v20170227-0935.jar
                   │   ├── org.eclipse.uml2.types-2.0.0.v20170227-0935.jar.md5
                   │   ├── org.eclipse.uml2.types-2.0.0.v20170227-0935.jar.sha1
                   │   ├── org.eclipse.uml2.types-2.0.0.v20170227-0935.pom
                   │   ├── org.eclipse.uml2.types-2.0.0.v20170227-0935.pom.md5
                   │   └── org.eclipse.uml2.types-2.0.0.v20170227-0935.pom.sha1
                   ├── maven-metadata-local.xml
                   ├── maven-metadata-local.xml.md5
                   └── maven-metadata-local.xml.sha1

7 directories, 9 files

...and pom.xml (generated) contains:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.eclipse.uml2.types</groupId>
  <artifactId>org.eclipse.uml2.types</artifactId>
  <version>2.0.0.v20170227-0935</version>
  <description>POM was created from install:install-file</description>
</project>

So, this is still tedious, but a lot less work than what I was imagining. Here's a script for the first set:

#!/bin/sh
../make-repo.sh ../lib \
          org.eclipse.mdht.emf.runtime-3.0.0.201802220601.jar \
          org.eclipse.mdht.emf.runtime \
          org.eclipse.mdht.emf.runtime \
          3.0.0.201802220601
../make-repo.sh ../lib \
          org.eclipse.mdht.uml.cda-3.0.0.201802220601.jar \
          org.eclipse.mdht.uml.cda \
          org.eclipse.mdht.uml.cda \
          3.0.0.201802220601
../make-repo.sh ../lib \
          org.eclipse.mdht.uml.hl7.datatypes-3.0.0.201802220601.jar \
          org.eclipse.mdht.uml.hl7.datatypes \
          org.eclipse.mdht.uml.hl7.datatypes \
          3.0.0.201802220601
../make-repo.sh ../lib \
          org.eclipse.mdht.uml.hl7.rim-3.0.0.201802220601.jar \
          org.eclipse.mdht.uml.hl7.rim \
          org.eclipse.mdht.uml.hl7.rim \
          3.0.0.201802220601
../make-repo.sh ../lib \
          org.eclipse.mdht.uml.hl7.vocab-3.0.0.201802220601.jar \
          org.eclipse.mdht.uml.hl7.vocab \
          org.eclipse.mdht.uml.hl7.vocab \
          3.0.0.201802220601
../make-repo.sh ../lib \
          org.openhealthtools.mdht.uml.cda.consol2-3.0.3.20180222.jar \
          org.openhealthtools.mdht.uml.cda.consol2 \
          org.openhealthtools.mdht.uml.cda.consol2 \
          3.0.3.20180222
../make-repo.sh ../lib \
          org.openhealthtools.mdht.uml.cda.mu2consol-3.0.3.20180222.jar \
          org.openhealthtools.mdht.uml.cda.mu2consol \
          org.openhealthtools.mdht.uml.cda.mu2consol \
          3.0.3.20180222

...and the second set:

#!/bin/sh
../make-repo.sh ../lib \
          ./org.eclipse.emf.common-2.12.0.v20160420-0247.jar \
          org.eclipse.emf.common \
          org.eclipse.emf.common \
          2.12.0.v20160420-0247
../make-repo.sh ../lib \
          ./org.eclipse.emf.ecore-2.12.0.v20160420-0247.jar \
          org.eclipse.emf.ecore \
          org.eclipse.emf.ecore \
          2.12.0.v20160420-0247
../make-repo.sh ../lib \
          ./org.eclipse.emf.ecore.xmi-2.12.0.v20160420-0247.jar \
          org.eclipse.emf.ecore.xmi \
          org.eclipse.emf.ecore.xmi \
          2.12.0.v20160420-0247
../make-repo.sh ../lib \
          ./org.eclipse.ocl-3.6.0.v20160523-1914.jar \
          org.eclipse.ocl \
          org.eclipse.ocl \
          3.6.0.v20160523-1914
../make-repo.sh ../lib \
          ./org.eclipse.ocl.common-1.4.0.v20160521-2033.jar \
          org.eclipse.ocl.common \
          org.eclipse.ocl.common \
          1.4.0.v20160521-2033
../make-repo.sh ../lib \
          ./org.eclipse.ocl.ecore-3.6.0.v20160523-1914.jar \
          org.eclipse.ocl.ecore \
          org.eclipse.ocl.ecore \
          3.6.0.v20160523-1914
../make-repo.sh ../lib \
          ./org.eclipse.uml2.common-2.1.0.v20170227-0935.jar \
          org.eclipse.uml2.common \
          org.eclipse.uml2.common \
          2.1.0.v20170227-0935

If you get an error such as...

The following artifacts could not be resolved:
org.eclipse.ocl:org.eclipse.ocl:jar:3.6.200.v20170522-1736
org.eclipse.ocl.common:org.eclipse.ocl.common:jar:1.4.200.v20160613-1518
org.eclipse.ocl.ecore:org.eclipse.ocl.ecore:jar:3.6.200.v20170522-1736
org.eclipse.mdht.uml.hl7.vocab:org.eclipse.mdht.uml.hl7.vocab:jar:3.0.0.202201250600:

Could not find artifact org.eclipse.ocl:org.eclipse.ocl:jar:3.6.200.v20170522-1736
in MDHT libraries (static) (file:///home/russ/sandboxes/mdht-restlet/lib)

...you likely need to verify that you populated the lib subdirectory with the missing JAR(s). Once that's done, and as Maven remembers past failures, you must "clear Maven's throat" as it were:

$ mvn -U clean package

Then you might find that the list of missing artifacts has diminished some. No one said this wasn't going to be tedious. Tell the supplier of the JARs you're using to get with the program and commit them to Maven Central or some repository somewhere.


How to create Maven local repositories (faster way)

I'm unsure of why someone would not use this way instead of the more complex way described above and below. At one point, I was convinced by something I read on stackoverflow.com that there were reasons not to do this, but once I got over it, I've used it quite a bit without consequence.

Simply add your dependency to a subdirectory off the project- (or module-) path like this:

project/
└── libs/
    └── cda2fhir-0.2.jar

...then reference it in pom.xml something like this:

<properties>
  <cda2fhir.version>0.2</cda2fhir.version>
</properties>

<dependencies>
  <dependency>
    <groupId>tr.com.srcdc</groupId>
    <artifactId>cda2fhir</artifactId>
    <version>${cda2fhir.version}</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/libs/cda2fhir-${cda2fhir.version}.jar</systemPath>
  </dependency>
  ...

How to create Maven local repositories

Here's an article on local, in-project Maven repository as a last resort in a great and enlightening discussion: Adding external/custom JARs into Maven project


How to create Maven local repositories

This is a simpler exposé than the one just above. It might be less muddy and better explained.

Here's how I went about making of an ad hoc and ancient JAR a participant in a Maven build. I didn't know much at all about this JAR except that it represents Acme's SimpleAPI and I have sample programs using it.

What do I have? File acmesimpleapi.jar which I know to be version 5.2. It contains the package, com.acme.simpleapi. I decided—and it's completely arbitrary—on com.acme as the groupId and acme-simpleapi as artifactId. These are needed in order to reference the JAR inside dependendencies in the pom.xml file as we'll see later on.

In my scratch directory, where I'm going to make this JAR into a proper Maven repository, I have:

russ@nargothrond ~/dev/acmesimpleapi $ ll
total 16
drwxrwxr-x 3 russ russ  4096 Aug 21 13:09 .
drwxrwxr-x 4 russ russ  4096 Aug 21 12:56 ..
drwxrwxr-x 3 russ russ  4096 Aug 21 13:03 lib                # empty lib directory
-rwxrwxr-x 1 russ russ   291 Aug 21 13:01 make-repo.sh       # my working script
-rwxrwx--- 1 russ russ 52246 Mar 15 12:03 acmesimpleapi.jar  # original JAR

My working script just invokes Maven to do the work of tucking the JAR away into proper Maven-repository garb. I'm annotating it for clarity (so, the green notes are not part of the script):

#!/bin/sh
mvn -e install:install-file \
    -DlocalRepositoryPath=./lib \  # where to put the results
    -DcreateChecksum=true \
    -Dpackaging=jar \
    -Dfile=./acmesimpleapi.jar \   # what the original JAR in hand is called
    -DgroupId=com.acme \           # what I decided the groupId is going to be
    -DartifactId=acme-simpleapi \  # what I decided the JAR is going to be called
    -Dversion=5.2 \
    -Dpackaging=jar \
    -DgeneratePom=true

I invoke my script. (The first time, lots of stuff gets brought down by Maven into ~/.m2/repository and shows up when you run this script, but I'm not showing that.

russ@nargothrond ~/dev/acmesimpleapi $ ./make-repo.sh
Warning: JAVA_HOME environment variable is not set.
[INFO] Error stacktraces are turned on.
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install-file (default-cli) @ standalone-pom ---
[INFO] pom.xml not found in acmesimpleapi.jar
[INFO] Installing /home/russ/dev/acmesimpleapi/acmesimpleapi.jar to \
            /home/russ/dev/acmesimpleapi/lib/com/acme/acme-simpleapi/5.2/acme-simpleapi-5.2.jar
[INFO] Installing /tmp/mvninstall3630215891605911268.pom to \
            /home/russ/dev/acmesimpleapi/lib/com/acme/acme-simpleapi/5.2/acme-simpleapi-5.2.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.613 s
[INFO] Finished at: 2018-08-21T13:19:39-06:00
[INFO] Final Memory: 9M/303M
[INFO] ------------------------------------------------------------------------

The result is something I could never have concocted purely by hand and it is Maven-correct:

russ@nargothrond ~/dev/acmesimpleapi $ tree lib
lib
└── com
    └── acme
        └── acme-simpleapi
            ├── 5.2
            │   ├── acme-simpleapi-5.2.jar
            │   ├── acme-simpleapi-5.2.jar.md5
            │   ├── acme-simpleapi-5.2.jar.sha1
            │   ├── acme-simpleapi-5.2.pom
            │   ├── acme-simpleapi-5.2.pom.md5
            │   └── acme-simpleapi-5.2.pom.sha1
            ├── maven-metadata-local.xml
            ├── maven-metadata-local.xml.md5
            └── maven-metadata-local.xml.sha1

4 directories, 9 files

I move that over into my IntelliJ IDEA project, under a lib subdirectory and it appears (identically to) just as above.

Now I get into the business of telling Maven about it via pom.xml. There are three sections in this file where I need to intervene:

  1. Create an entry in <properties> for the version, 5.2. That way, we can change the version easily in future.
  2. Inform Maven that a local repository is nearby with JAR(s) inside. The name is insignificant—I'm making it very descriptive.
  3. Signal the dependency itself.

This is the pom.xml file insofar as the SimpleAPI JAR is concerned. I'm using dots where there is surely more content in a real pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                        http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme.fireworks</groupId>
  <artifactId>untitled</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <!-- #1 above: macro for version, just something I obsess over -->
    <acme-simpleapi-version>5.2</acme-simpleapi-version>
    .
    .
    .
  </properties>

  <repositories>
    <!-- #2 above: how you tell Maven where the local repository is-->
    <repository>
      <id>acme-simpleapi</id>
      <name>Acme SimpleAPI JAR</name>
      <url>file://${project.basedir}/lib</url>
    </repository>
    .
    .
    .
  </repositories>

  <dependencies>
    <!-- #3 above: how you define the dependency so it gets built into project -->
    <dependency>
      <groupId>com.acme</groupId>
      <artifactId>acme-simpleapi</artifactId>
      <version>${acme-simpleapi-version}</version>
    </dependency>
    .
    .
    .
  </dependencies>

  .
  .
  .
</project>

Browsing the JAR code in the project

This isn't merely relevant to what we've done here, but also to browsing external code of any sort. IntelliJ IDEA keeps all the external contributing components to a project, including JDK- and third-party JARs and also a JAR like the one we've rigged into its own, local repository here, under External Libraries in the Project pane.

The hierarchy and even class files can be browsed. The class files open with code recreated from the .class file. Also, an option to navigate to the real source code, if you have it, is available in the upper right-hand corner of the editor, on the same yellow line as "Decompile .class file, bytecode version:". This option appears as Download Sources and Choose Sources....


Could not resolved dependencies ... not reattempted until ...

If trying to create a static local repository for Maven, you may be doing this as a last resort after trying to get the JAR in from Maven Central or another repository. This left artifacts on the path ~/.m2/repository/... and you now get an error like:

Could not resolve dependencies for project <project-name>: \
<JAR-or-so-name> was not found in file:///home/<user>/<path>/<project>/lib \
during a previous attempt. This failure was cached in the local repository and resolution \
is not reattempted until the update interval of <JAR> library (static) has elapsed or updates are forced

The solution is to go to ~/.m2/repository/... and remove the failed import from Maven Central (or other), then try your build again.

Maven is patiently awaiting the JAR to get filled into your local Maven repository (on path ~/.m2/repository) and won't switch to your static local one (in the project probably in subdirectory lib until you stop Maven from trying to get it from the normal place.


More on using Maven to generate repository filesystem...

...including scripts. The first one will put the JAR into Nexus. The second will put it to anywhere on the local filesystem.

nexus-deploy.sh:
#!/bin/bash
# ------------------------------------------------------------------------
# Deploy JAR artifacts to the Sonatype Nexus artifactory. It's not
# practical or recommended to try to do this with curl.
#
# Notes:
# "repositoryId" corresponds to the list of servers in ~/.m2/settings.xml.
# There are only two, legitimate repository targets; both are "hosted"
# repositories:
#
#   a. maven-snapshots, an internal place for unversioned (SNAPSHOTs)
#       artifacts. Only artifacts with a version ending in -SNAPSHOT
#       may go here.
#   b. maven-releases, an internal place for versioned artifacts. (This
#       is the default.
#
# Russell Bateman
# May 2019
# ------------------------------------------------------------------------
set -u  # (catch uninitialized variables when encountered)
         BOLD=$(tput bold)
        PLAIN=$(tput sgr0)
         TRUE=1
        FALSE=0
    NEXUS_URL=http://maven.acme.com:8081/repository/maven-releases/
 REPOSITORYID=maven-releases
     POM_FILE=
      GROUPID=
   ARTIFACTID=
      VERSION=
     JAR_FILE=unknown.jar
POM_GENERATED=$FALSE
      verbose=$TRUE
      pretend=$FALSE

DoHelp()
{
  echo "\
    +----------------------------------------------------------+
    | Deploy JAR packaging to Sonatype Nexus Maven artifactory |
    +----------------------------------------------------------+
$0 plus these required and optional arguments:
Options:
    -h display this help blurb
    -n execute nothing, but show what will be done (dry run)
    -q suppress all output

    -u       (default: $NEXUS_URL)
    -r    (default: maven-releases)
    -p  (optional, then no -gav)
    -g         (required)
    -a      (required)
    -v         (required)
Arguments:
    path-to-JAR         (required)
  "
}

GeneratePomXml()
{
  # gotta have, especially, the distributionManagement paragraph
  cat > pom.xml << 'end'
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>nothing</groupId>
  <artifactId>nothing</artifactId>
  <version>nothing</version>
  <description>Temporary pom.xml to make Maven work.</description>

  <distributionManagement>
    <repository>
      <id>maven-releases</id>
      <name>maven-releases</name>
      <url>http://maven.acme.com:8081/repository/maven-releases</url>
      <layout>default</layout>
    </repository>
    <snapshotRepository>
      <id>maven-snapshots</id>
      <name>maven-snapshots</name>
      <url>http://maven.acme.com:8081/repository/maven-snapshots/</url>
      <layout>default</layout>
    </snapshotRepository>
  </distributionManagement>
</project>
end
  POM_GENERATED=$TRUE
}

ErasePomXml()
{
  rm pom.xml
}


if [ -z "$*" ]; then
  DoHelp
fi

set -- `getopt hnqu:p:g:a:v:j: $*`
while [ $1 != -- ]; do
  case $1 in
    -h) DoHelp $*       ;  exit 0   ;;
    -n)      pretend=$TRUE          ;;
    -q)      verbose=$FALSE         ;;
    -u)    NEXUS_URL=$2 ;  shift ;;
    -r) REPOSITORYID=$2 ;  shift ;;
    -p)     POM_FILE=$2 ;  shift ;;
    -g)      GROUPID=$2 ;  shift ;;
    -a)   ARTIFACTID=$2 ;  shift ;;
    -v)      VERSION=$2 ;  shift ;;
    *)  echo "WARNING: ignoring invalid options ($1)..." ;;
  esac
  shift
done
shift

MAVEN=`which mvn`

if [ -z "$MAVEN" ]; then
  echo "Missing Maven--must install"
  exit 0
fi
if [ -z "$*" ]; then
  echo "Missing JAR filepath"
  exit 0
fi
if [ -z "$NEXUS_URL" ]; then
  echo "Missing artifactory URL"
  exit 0
fi
if [ -z "$REPOSITORYID" ]; then
  echo "Missing repositoryId"
  exit 0
fi
if [ -z "$GROUPID" ]; then
  if [ -n "$POM_FILE" ]; then
    echo "Using specified custom pom.xml to get groupId"
  else
    echo "Missing group id"
    exit 0
  fi
fi
if [ -z "$ARTIFACTID" ]; then
  if [ -n "$POM_FILE" ]; then
    echo "Using specified custom pom.xml to get artifactId"
  else
    echo "Missing artifact id"
    exit 0
  fi
fi
if [ -z "$VERSION" ]; then
  if [ -n "$POM_FILE" ]; then
    echo "Using specified custom pom.xml to get version"
  else
    echo "Missing artifact version"
    exit 0
  fi
fi
if [ -z "$JAR_FILE" ]; then
  echo "Missing artifact (JAR) name"
  exit 0
fi

JAR_FILE="${1:-}"
if [ $verbose -eq $TRUE ]; then
  echo "Deploying $JAR_FILE to Sonatype Nexus repository."
fi

if [ -n "$POM_FILE" ]; then
  printf "\
    ${BOLD}mvn deploy:deploy-file${PLAIN}\n\
        ${BOLD}-Dpackaging=${PLAIN}jar              \n\
        ${BOLD}-DgeneratePom=${PLAIN}false          \n\
        ${BOLD}-DpomFile=${PLAIN}$POM_FILE          \n\
        ${BOLD}-Dfile=${PLAIN}$JAR_FILE             \n\
        ${BOLD}-DrepositoryId=${PLAIN}$REPOSITORYID \n\
        ${BOLD}-Durl=${PLAIN}$NEXUS_URL\n"
else
  printf "\
    ${BOLD}mvn deploy:deploy-file${PLAIN}\n\
        ${BOLD}-Dpackaging=${PLAIN}jar              \n\
        ${BOLD}-DgeneratePom=${PLAIN}true           \n\
        ${BOLD}-DgroupId=${PLAIN}$GROUPID           \n\
        ${BOLD}-DartifactId=${PLAIN}$ARTIFACTID     \n\
        ${BOLD}-Dversion=${PLAIN}$VERSION           \n\
        ${BOLD}-Dfile=${PLAIN}$JAR_FILE             \n\
        ${BOLD}-DrepositoryId=${PLAIN}$REPOSITORYID \n\
        ${BOLD}-Durl=${PLAIN}$NEXUS_URL\n"
fi
if [ $pretend -ne $TRUE ]; then
  GeneratePomXml
  if [ -n "$POM_FILE" ]; then
    mvn deploy:deploy-file         \
      -Dpackaging=jar              \
      -DgeneratePom=false          \
      -DpomFile=${POM_FILE}        \
      -Dfile=$JAR_FILE             \
      -DrepositoryId=$REPOSITORYID \
      -Durl=$NEXUS_URL
  else
    mvn deploy:deploy-file         \
      -Dpackaging=jar              \
      -DgeneratePom=true           \
      -DgroupId=$GROUPID           \
      -DartifactId=$ARTIFACTID     \
      -Dversion=$VERSION           \
      -Dfile=$JAR_FILE             \
      -DrepositoryId=$REPOSITORYID \
      -Durl=$NEXUS_URL
  fi
  if [ $POM_GENERATED -eq $TRUE ]; then
    ErasePomXml
  fi
fi
# vim: set tabstop=2 shiftwidth=2 noexpandtab:


local-deploy.sh:
#!/bin/bash
# ------------------------------------------------------------------------
# Deploy JAR artifacts to the local filesystem on a specified path.
#
# Russell Bateman
# May 2019
# ------------------------------------------------------------------------
set -u  # (catch uninitialized variables when encountered)
         BOLD=$(tput bold)
        PLAIN=$(tput sgr0)
         TRUE=1
        FALSE=0
  TARGET_PATH=
     POM_FILE=
      GROUPID=
   ARTIFACTID=
      VERSION=
     JAR_FILE=unknown.jar
      verbose=$TRUE
      pretend=$FALSE

DoHelp()
{
  echo "\
    +------------------------------------------+
    | Deploy JAR packaging to local filesystem |
    +------------------------------------------+
$0 plus these required and optional arguments:
Options:
    -h display this help blurb
    -n execute nothing, but show what will be done (dry run)
    -q suppress all output

    -u     (required)
    -p  (optional, then no -gav)
    -g         (required)
    -a      (required)
    -v         (required)
Arguments:
    path-to-JAR         (required)
  "
}

if [ -z "$*" ]; then
  DoHelp
fi

set -- `getopt hnqu:p:g:a:v:j: $*`
while [ $1 != -- ]; do
  case $1 in
    -h) DoHelp $*       ;  exit 0   ;;
    -n)      pretend=$TRUE          ;;
    -q)      verbose=$FALSE         ;;
    -u)  TARGET_PATH=$2 ;  shift ;;
    -p)     POM_FILE=$2 ;  shift ;;
    -g)      GROUPID=$2 ;  shift ;;
    -a)   ARTIFACTID=$2 ;  shift ;;
    -v)      VERSION=$2 ;  shift ;;
    *)  echo "WARNING: ignoring invalid options ($1)..." ;;
  esac
  shift
done
shift

MAVEN=`which mvn`

if [ -z "$MAVEN" ]; then
  echo "Missing Maven--must install"
  exit 0
fi
if [ -z "$*" ]; then
  echo "Missing JAR filepath"
  exit 0
fi
if [ -z "$TARGET_PATH" ]; then
  echo "Missing local-repository path"
  exit 0
fi
if [ -z "$GROUPID" ]; then
  if [ -n "$POM_FILE" ]; then
    echo "Using specified custom pom.xml to get groupId"
  else
    echo "Missing group id"
    exit 0
  fi
fi
if [ -z "$ARTIFACTID" ]; then
  if [ -n "$POM_FILE" ]; then
    echo "Using specified custom pom.xml to get artifactId"
  else
    echo "Missing artifact id"
    exit 0
  fi
fi
if [ -z "$VERSION" ]; then
  if [ -n "$POM_FILE" ]; then
    echo "Using specified custom pom.xml to get version"
  else
    echo "Missing artifact version"
    exit 0
  fi
fi
if [ -z "$JAR_FILE" ]; then
  echo "Missing artifact (JAR) name"
  exit 0
fi

JAR_FILE="${1:-}"
if [ $verbose -eq $TRUE ]; then
  echo "Deploying $JAR_FILE to Sonatype Nexus repository."
fi

if [ -n "$POM_FILE" ]; then
  printf "\
    ${BOLD}mvn install:install-file${PLAIN}\n\
        ${BOLD}-Dpackaging=${PLAIN}jar              \n\
        ${BOLD}-DgeneratePom=${PLAIN}false          \n\
        ${BOLD}-DcreateChecksum=${PLAIN}true        \n\
        ${BOLD}-Dfile=${PLAIN}$JAR_FILE             \n\
        ${BOLD}-DlocalRepositoryPath=${PLAIN}$TARGET_PATH\n"
else
  printf "\
    ${BOLD}mvn install:install-file${PLAIN}\n\
        ${BOLD}-Dpackaging=${PLAIN}jar              \n\
        ${BOLD}-DgeneratePom=${PLAIN}true           \n\
        ${BOLD}-DcreateChecksum=${PLAIN}true        \n\
        ${BOLD}-DgroupId=${PLAIN}$GROUPID           \n\
        ${BOLD}-DartifactId=${PLAIN}$ARTIFACTID     \n\
        ${BOLD}-Dversion=${PLAIN}$VERSION           \n\
        ${BOLD}-Dfile=${PLAIN}$JAR_FILE             \n\
        ${BOLD}-DlocalRepositoryPath=${PLAIN}$TARGET_PATH\n"
fi
if [ $pretend -ne $TRUE ]; then
  if [ -n "$POM_FILE" ]; then
    mvn install:install-file       \
      -Dpackaging=jar              \
      -DgeneratePom=false          \
      -DcreateChecksum=true        \
      -Dfile=$JAR_FILE             \
      -DlocalRepositoryPath=$TARGET_PATH
  else
    mvn install:install-file       \
      -Dpackaging=jar              \
      -DgeneratePom=true           \
      -DcreateChecksum=true        \
      -DgroupId=$GROUPID           \
      -DartifactId=$ARTIFACTID     \
      -Dversion=$VERSION           \
      -Dfile=$JAR_FILE             \
      -DlocalRepositoryPath=$TARGET_PATH
  fi
fi
# vim: set tabstop=2 shiftwidth=2 noexpandtab:

hostname in certificate didn't match

You see this in Maven:

[ERROR]     Unresolveable build extension: Plugin org.apache.nifi:nifi-nar-maven-plugin:1.0.1-incubating or one of its
dependencies could not be resolved: Failed to read artifact descriptor for
org.apache.nifi:nifi-nar-maven-plugin:jar:1.0.1-incubating: Could not transfer artifact
org.apache.nifi:nifi-nar-maven-plugin:pom:1.0.1-incubating from/to central (https://repo.maven.apache.org/maven2):
hostname in certificate didn't match: &tl;repo.maven.apache.org> != &tl;repo1.maven.org> OR &tl;repo1.maven.org> -> [Help 2]

It's likely something very temporary broken at the repository server that will be fixed soon, but in the meantime, add this to your Maven command line:

-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true

Symbol TestName causes "org.junit.rules does not exist"

You see this in Maven (or IntelliJ IDEA):

package org.junit does not exist

because you're using TestName. Why?

It's because TestName is fairly recent to JUnit (don't know the exact version it was introduced) and Maven is getting an older version.

Even if you specify the latest version of JUnit:

<properties>
  <junit.version>4.12</junit.version>
.
.
.
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>[${junit.version}]</version>
  </dependency>
</dependencies>

...you may not be getting (v4.12) since specifying

  <version>4.12</version>

...only means "allow anything, but prefer 4.12." When a conflict is detected, Maven is allowed to "choose the best version." That won't be v4.12 unless it happens to be present in ~/.m2/repository/org/junit.

To force v4.12 (and Maven downloading it), use brackets:

<properties>
  <junit.version>4.12</junit.version>
.
.
.
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>[${junit.version}]</version>
  </dependency>
</dependencies>

Maven "lastUpdated" problem...

You invoke the build and get this message at some point:

[ERROR] Failed to execute goal on project <project name>: Could not resolve dependencies for project
    <some project- or module name and version>
    Failure to find org.apache.commons:commons-io:jar:2.5 in
    file:///home/russ/dev/<some Maven repository path>
    was cached in the local repository, resolution will not be reattempted until the update interval of
    base-library has elapsed or updates are forced

You look at ~/.m2/repository/org/apache/commons/commons-io and see:

~/.m2/repository/org/apache/commons/commons-io $ tree
.
└─ 2.5
    ├─ commons-io-2.5.jar.lastUpdated
    └─ commons-io-2.5.pom.lastUpdated

You use mvn -U clean compile, etc. to solve the problem because that's what the folks on stackoverflow.com are saying, but to no avail. What to do?

In my case, I remembered a development host I had that probably had this artifact on it and, sure enough, I found it under the version 1.3.2. I copied it and surgically implanted it in my local repository:

~/.m2/repository/org/apache/commons/commons-io $ tree
.
├─ 1.3.2
│   ├─ commons-io-1.3.2.pom
│   ├─ commons-io-1.3.2.pom.sha1
│   └─ _maven.repositories
└─ 2.5
    ├─ commons-io-2.5.jar.lastUpdated
    └─ commons-io-2.5.pom.lastUpdated

commons-io.jar: Apache Commons I/O not in Maven?

My code requires commons-io-2.5.jar where I get some classes and/or methods that I cannot get from commons-io-1.3.2.jar. Well, this is a long and nasty journey. You can skip to the end to see how simple a problem it was, but the journey itself is instructive to see how I tried to debug it.

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.5</version>
</dependency>

When I attempt a build of the project requiring the later JAR, I get this error from Maven. (I'm doing a little wrapping and highlighting to make this clearer):

.
.
.
Downloading: https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/2.5/commons-io-2.5.jar
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] Etretat Logiciels NiFi Pipeline .................... SUCCESS [  0.777 s]
[INFO] nifi-shared ........................................ SUCCESS [  0.078 s]
[INFO] cda-filter ......................................... SUCCESS [  0.027 s]
[INFO] fhir-processors .................................... FAILURE [  1.148 s]
[INFO] legacy ............................................. SKIPPED
[INFO] medical-filter ..................................... SKIPPED
[INFO] jdbc ............................................... SKIPPED
[INFO] standard-processors ................................ SKIPPED
[INFO] el-nifi-nar ........................................ SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.305 s
[INFO] Finished at: 2017-03-29T15:03:09-06:00
[INFO] Final Memory: 28M/603M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project fhir-processors:
    Could not resolve dependencies for project com.etretatlogiciels.nifi.pipeline:fhir-processors:jar:1.0.0:
    Could not find artifact org.apache.commons:commons-io:jar:2.5
       in data-local (file:///home/russ/dev/nifi-pipeline.v73_release.dev/code/nifi-pipeline/fhir-processors/lib)
.
.
.

It's what's happening a few lines above that tells the story (in green): Maven has gone to Apache's Maven repository to fetch the requested JAR (but, it wasn't there). Let's demonstrate why using wget on that path:

$ wget https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/2.5/commons-io-2.5.jar
--2017-03-29 15:05:41--  https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/2.5/commons-io-2.5.jar
Resolving repo.maven.apache.org (repo.maven.apache.org)... 151.101.48.215
Connecting to repo.maven.apache.org (repo.maven.apache.org)|151.101.48.215|:443... connected.
HTTP request sent, awaiting response... 404 Not Found
2017-03-29 15:05:42 ERROR 404: Not Found.

In a web browser, https://repo.maven.apache.org/maven2/org/apache/commons/commons-io/ displays that only version 1.3.2 is even there:

Index of /maven2/org/apache/commons/commons-io/

../
1.3.2/                                   2012-12-20 06:21                    -
maven-metadata.xml                       2007-07-02 14:32                  304
maven-metadata.xml.md5                   2007-07-02 14:32                   32
maven-metadata.xml.sha1                  2007-07-02 14:32                   40

...and, when you look inside at what is really there:

Index of /maven2/org/apache/commons/commons-io/1.3.2

../
commons-io-1.3.2.jar                     2007-07-02 14:32                87776
commons-io-1.3.2.jar.asc                 2007-06-26 20:59                  194
commons-io-1.3.2.jar.asc.md5             2010-11-11 22:50                   32
commons-io-1.3.2.jar.asc.sha1            2010-11-11 22:50                   40
commons-io-1.3.2.jar.md5                 2007-07-02 14:32                   32
commons-io-1.3.2.jar.sha1                2007-07-02 14:32                   40
commons-io-1.3.2-javadoc.jar             2007-07-02 14:31               383040
commons-io-1.3.2-javadoc.jar.asc         2007-06-26 20:58                  194
commons-io-1.3.2-javadoc.jar.asc.md5     2010-11-11 22:50                   32
commons-io-1.3.2-javadoc.jar.asc.sha1    2010-11-11 22:50                   40
commons-io-1.3.2-javadoc.jar.md5         2007-07-02 14:31                   32
commons-io-1.3.2-javadoc.jar.sha1        2007-07-02 14:31                   40
commons-io-1.3.2.pom                    2012-10-09 14:28                 640
commons-io-1.3.2.pom.asc                 2012-10-09 14:34                  189
commons-io-1.3.2.pom.asc.md5             2012-10-09 14:34                   32
commons-io-1.3.2.pom.asc.sha1            2012-10-09 14:34                   40
commons-io-1.3.2.pom.md5                 2012-10-09 14:29                   32
commons-io-1.3.2.pom.sha1                2012-10-09 14:29                   40
commons-io-1.3.2-sources.jar             2007-07-02 14:31               135544
commons-io-1.3.2-sources.jar.asc         2007-06-26 20:58                  194
commons-io-1.3.2-sources.jar.asc.md5     2010-11-11 22:50                   32
commons-io-1.3.2-sources.jar.asc.sha1    2010-11-11 22:50                   40
commons-io-1.3.2-sources.jar.md5         2007-07-02 14:31                   32
commons-io-1.3.2-sources.jar.sha1        2007-07-02 14:31                   40

...by looking at the contents of commons-io-1.3.2.pom, you see that:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
    <distributionManagement>
    <relocation>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <message>https://issues.sonatype.org/browse/MVNCENTRAL-244</message>
    </relocation>
  </distributionManagement>
</project>

Apache tells Maven, even though the JARs are sitting right there, to go get them from Maven Central!

The work-around...

So, taking a hint from this, I created v2.5 in the existing file system structure of my local Maven repository:

~/.m2/repository/org/apache/commons/commons-io $ tree
.
├─ 1.3.2
│   ├─ commons-io-1.3.2.pom
│   ├─ commons-io-1.3.2.pom.sha1
│   └─ _maven.repositories
└─ 2.5
    └─ commons-io-2.5.pom

...where commons-io-2.5.pom is nothing more than a copy of what's in commons-io-1.3.2.pom with its version changed. That way, Maven goes to Maven Central to get this JAR.

Magically (not!), my build begins to work.

The real answer is nothing more than a mistaken groupId, as I learned after a visit to the mailing list, [email protected]. The correct dependency specification is:

<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.5</version>
</dependency>

In my defence, please note that there are other Apache Commons products that contain "apache" as an element in the groupId. I actually, brainlessly thought they were all this way.

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-csv</artifactId>
  <version>1.4</version>
</dependency>

Echoing Maven property definitions

Who can say why Maven implementors steadfastly refuse to make it easy to list out the value of properties?

Put this as its own target in the <build> paragraph of your pom.xml. Do not put this under <pluginManagement ../>, but merely under <plugins .../> or it will not work:

<build>
  <plugins>
    .
    .
    .
    <plugin>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>1.8</version>
      <executions>
        <execution>
          <phase>compile</phase>
          <configuration>
            <target>
              <echoproperties />
            </target>
          </configuration>
          <goals>
            <goal>run</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    .
    .
    .
  </plugins>
</build>

Next, when you run Maven, do this:

$ mvn clean compile | grep echoproperties | awk '{print $2}'

This will list out the properties, something really huge and unmanageable like this (I'm showing just a few here) because many lines are duplicates (depending on the complexity, number of submodules, etc. of your project):

ant.java.version=1.8
ant.project.default-target=main
ant.project.name=maven-antrun-
ant.version=Apache
awt.toolkit=sun.awt.X11.XToolkit
basedir=/home/russ/dev/nifi-pipeline.v74_release.dev/code/nifi-pipeline
cassandra-unit.version=3.1.3.2
cassandra.version=3.9
java.vendor=Oracle
java.vendor.url=http\://java.oracle.com/
java.version=1.8.0_112
line.separator=\n
maven.compiler.plugin.version=3.5.1
maven.compiler.source=1.8
maven.compiler.target=1.8
os.arch=amd64
os.name=Linux
os.version=4.8.0-53-generic
path.separator=\:
junit.version=4.12
user.country=US
user.home=/home/russ
user.language=en
user.name=russ
user.timezone=America/Denver

You can eliminate duplication and also sort what comes out by adding sort -u:

$ mvn clean compile | grep echoproperties | awk '{print $2}' | sort -u

To look for just one property, do this:

$ mvn clean compile | grep echoproperties | awk '{print $2}' | sort -u | grep property-name

'dependencies.dependency.version' for [...] jar is missing

Building using mvn clean -U package, I have been getting:

~/dev/mdht-restlet $ mvn clean -U package
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.version' for lpg.runtime.java:lpg.runtime.java:jar is missing. @ line 49, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.emf.ecore.xmi:org.eclipse.emf.ecore.xmi:jar is missing. @ line 53, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.emf.ecore:org.eclipse.emf.ecore:jar is missing. @ line 57, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.emf.common:org.eclipse.emf.common:jar is missing. @ line 61, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.ocl:org.eclipse.ocl:jar is missing. @ line 65, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.ocl.common:org.eclipse.ocl.common:jar is missing. @ line 69, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.ocl.ecore:org.eclipse.ocl.ecore:jar is missing. @ line 73, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.uml2.types:org.eclipse.uml2.types:jar is missing. @ line 77, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.mdht:org.eclipse.mdht.emf.runtime:jar is missing. @ line 82, column 17
[WARNING] 'dependencies.dependency.scope' for org.eclipse.mdht:org.eclipse.mdht.cda-runtime:pom must be one of [provided, compile,
    runtime, test, system] but is 'import'. @ line 92, column 14
[ERROR] 'dependencies.dependency.version' for org.eclipse.mdht:org.eclipse.mdht.uml.hl7.vocab:jar is missing. @ line 94, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.mdht:org.eclipse.mdht.uml.hl7.datatypes:jar is missing. @ line 98, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.mdht:org.eclipse.mdht.uml.hl7.rim:jar is missing. @ line 102, column 17
[ERROR] 'dependencies.dependency.version' for org.eclipse.mdht:org.eclipse.mdht.uml.cda:jar is missing. @ line 106, column 17
[ERROR] 'dependencies.dependency.version' for org.openhealthtools.mdht.cda:org.openhealthtools.mdht.uml.cda.consol2:jar is missing. @ line 111, column 17

It seems to want version numbers. In pom.xml, I had no version numbers:

  <dependencies>
    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-bundle</artifactId>
      <version>1.19.1</version>
    </dependency>
    <!-- MDHT third-party dependencies -->
    <dependency>
      <groupId>lpg.runtime.java</groupId>
      <artifactId>lpg.runtime.java</artifactId>
    </dependency>
    <dependency>
      <groupId>org.eclipse.emf.ecore.xmi</groupId>
      <artifactId>org.eclipse.emf.ecore.xmi</artifactId>
    </dependency>
    .
    .
    .
    <!-- MDHT Core dependencies -->
    <dependency>
      <groupId>org.eclipse.mdht</groupId>
      <artifactId>org.eclipse.mdht.emf.runtime</artifactId>
    </dependency>
    <!-- MDHT CDA dependencies -->
    <dependency>
      <groupId>org.eclipse.mdht</groupId>
      <artifactId>org.eclipse.mdht.cda-runtime</artifactId>
      <version>3.0.0-SNAPSHOT</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>
      <groupId>org.eclipse.mdht</groupId>
      <artifactId>org.eclipse.mdht.uml.hl7.vocab</artifactId>
    </dependency>
    .
    .
    .
    <!-- Various other dependencies -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

Now, looking carefully again at pom.xml in a working sample project, I see some special, dependency-management sauce:

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.eclipse.mdht</groupId>
        <artifactId>org.eclipse.mdht.cda-runtime</artifactId>
        <version>3.0.0-SNAPSHOT</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
    .
    .
    .

...where org.eclipse.mdht.cda-runtime does not appear in the simple dependencies list. Restoring this combination to mdht-restlet, things start to work. Now, just what is going on here? There is the presence of <dependencyManagement> and <scope>. I'm not certain how to describe what I've accomplished by this.


A build timestamp and/or version, etc. for MANIFEST.MF

In the maven-war-plugin <configuration>, add these lines. The highlighted line will lead to a Build-Time: timestamp.

<!-- Magic for maintaining a build timestamp in MANIFEST.MF: -->
<archive>
  <manifest>
    <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
    <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
  </manifest>
  <manifestEntries>
    <Build-Time>${maven.build.timestamp}</Build-Time>
  </manifestEntries>
</archive>

Here's the resulting mdht-restlet.war/META-INF/MANIFEST.MF file:

Manifest-Version: 1.0
Implementation-Title: mdht-restlet
Implementation-Version: 1.0.0-SNAPSHOT
Built-By: russ
Specification-Title: mdht-restlet
Implementation-Vendor-Id: com.acme.mdht
Build-Time: 2018-06-29T20:08:22Z
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_144
Specification-Version: 1.0

Now, in order to communicate this timestamp at runtime, you use this code to read the manifest file:

private String getBuildTimestamp( ServletContext servletContext )
{
  try
  {
    Manifest manifest = new Manifest( servletContext.getResourceAsStream( "/META-INF/MANIFEST.MF" ) );
    Attributes attributes = manifest.getMainAttributes();
    return attributes.getValue( "Build-Time" );
  }
  catch( IOException ex )
  {
    ...
  }
}

Hierarchical pom.xml files in projects

See note just below.


Non-resolvable parent POM for ... Could not find artifact ... parent.relativePath points at wrong local POM

This usually happens when using Maven to manage hierarchical projects with multiple submodules (IntelliJ IDEA parlance) or [sub]projects (Eclipse parlance).

There are a number of things to look for here, the most important being the exact relationship between the "parent" and "child" (or subdirectory) pom.xml files. The elements of this cooperative synchronization are (in this NiFi archive example):

  • Allow the parent pom.xml to specify
      <groupId>com.windofkeltia.nifi.processor</groupId>
      <artifactId>my-nifi-processor</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>pom</packaging>
    
  • Exactly replicate the parent specification in the children pom.xml files. They must exactly reflect the parent specification above:
      <artifactId>my-processor</artifactId>
      <packaging>jar</packaging>
    
      <parent>
        <groupId>com.windofkeltia.nifi.processor</groupId>
        <artifactId>my-nifi-processor</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
      </parent>
    
    Second child:
      <artifactId>my-nar</artifactId>
      <packaging>nar</packaging>
    
      <parent>
        <groupId>com.windofkeltia.nifi.processor</groupId>
        <artifactId>my-nifi-processor</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
      </parent>
    
  • Also, the child pom.xml files convey their contributing role (jar, nar) via their packaging specification and have their own artifact identities while evoking the parent's artifact identity.

  • On the face of it, the error that prompted this note seems to relate mostly to the relativePath specification. Indeed, this is important, but not everything.

  • Even things of the smallest importance, if not matching, like 1.0-SNAPSHOT versus 1.0.0-SNAPSHOT, will generate Maven errors that will not be noted a) with anything like the offending line number and b) with any wording evocative of what's really wrong.
  • On a less critical note, it's a good idea to keep properties inside the parent pom.xml and not in the children, which will inherit them. That way, you're not getting disparate versions of supporting JARs.

In this example, a NiFi archive (.nar) is built from one or more .jar files containing the processor code as governed by Maven through the root pom.xml file (cf. the packaging specifications).


Multimodule with NAR, RPM, etc.

Assume a multimodule project structure like this one. It's to build a custom NiFi processor (.nar) and then put it into an RPM for distribution (.rpm).

project
├── nar
│   └── pom.xml
├── pom.xml
└── processor
    ├── pom.xml
    └── src

It's important not to give conflicting artifactIds to these modules. For example, the above could be named (should be named something like):

  • top-level pom.xml: <artifactId>project</artifactId>
  • nar-building pom.xml: <artifactId>project-nar</artifactId>
  • processor-building Java source code pom.xml: <artifactId>processor</artifactId>

What's in pom.xml?

Here are some essentials (not full POMs) in the pom.xml files:

root/pom.xml:

Note properties for entire project (because root), module list and build statements.

<groupId>com.company.project</groupId>
<artifactId>project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<description>Project for custom processor</description>
<url>http://company.com</url>

<modules>
  <module>processor</module>
  <module>nar</module>
</modules>
.
.
.
<properties>
  <nifi-nar-maven-plugin.version>1.3.1</nifi-nar-maven-plugin.version>
  <rpm-maven-plugin.version>2.2.0</rpm-maven-plugin.version>
  <versions-maven-plugin.version>2.7</versions-maven-plugin.version>
</properties>
.
.
.
<build>
  <plugins>
    <plugin>
      <!-- What builds the NAR file... -->
      <groupId>org.apache.nifi</groupId>
      <artifactId>nifi-nar-maven-plugin</artifactId>
      <version>${nifi-nar-maven-plugin.version}</version>
      <extensions>true</extensions>
      <executions>
        <execution>
          <id>default-nar</id>
          <phase>package</phase>
          <goals>
            <goal>nar</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>rpm-maven-plugin</artifactId>
        <version>${rpm-maven-plugin.version}</version>
        <executions>
          <execution>
            <id>generate-rpm</id>
            <phase>package</phase>
            <goals>
              <goal>rpm</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <name>(whatever—includes an artifactId)</name>
          <version>(version)</version>
          <release>(release)</release>
          <group>Application/System</group>
          <vendor>Company name</vendor>
          <license>Proprietary</license>
          <url>https://company.com</url>
          <summary>(what this custom NiFi processor does)</summary>
          <mappings>
            <mapping>
              <directory>/opt/nifi/lib</directory>
              <directoryIncluded>false</directoryIncluded>
              <filemode>444</filemode>
              <username>root</username>
              <groupname>root</groupname>
              <sources>
                <source>
                  <location>${project.build.directory}</location>
                  <includes>
                    <include>*.nar</include>
                  </includes>
                </source>
              </sources>
            </mapping>
          </mappings>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
        <version>${versions-maven-plugin.version}</version>
        <configuration>
          <excludes>
            <exclude>org.apache.commons:commons-collections4</exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</build>
nar/pom.xml:

This builds the NAR and also the RPM.

<artifactId>project-nar</artifactId>
<version>1.0.0</version>
<packaging>nar</packaging>
.
.
.
<parent>
  <groupId>com.company.project</groupId>
  <artifactId>project</artifactId>
  <version>1.0.0</version>
  <relativePath>../pom.xml</relativePath>
</parent>
.
.
.
<properties>
  <code.root>../..</code.root>
  <maven.javadoc.skip>true</maven.javadoc.skip>
  <source.skip>true</source.skip>
</properties>
.
.
.
<dependencies>
  <dependency>
    <groupId>com.company.project</groupId>
    <artifactId>processor</artifactId>
    <version>${project.version}</version>
  </dependency>
</dependencies>
.
.
.
<build>
  <plugins>
    <!-- Bundle this all up in an RPM... -->
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>rpm-maven-plugin</artifactId>
      <version>${rpm-maven-plugin.version}</version>
      <executions>
        <execution>
          <id>generate-rpm</id>
          <goals>
            <goal>rpm</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
project/pom.xml:
<artifactId>processor</artifactId>
<packaging>jar</packaging>
.
.
.
<parent>
  <groupId>com.windofkeltia.leapyear</groupId>
  <artifactId>project</artifactId>
  <version>1.0.0</version>
  <relativePath>../pom.xml</relativePath>
</parent>
.
.
.
<properties>
  <code.root>../..</code.root>
</properties>

See values of properties/macros/variables/etc.

When you run, you wonder what, for example, ${project.build.directory} or similar macros evaluate to. How to see even an ugly display of their values when Maven is running? (Refer to the project shown just above for an understanding of the multiple modules you see here.)

A solution is to add the following paragraph as a plugin to the build section of pom.xml. It's <echoproperties /> that does it.

<plugin>
  <!-- How to see properties (values for macros, variables; whatever you wish to call them)... -->
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>${maven-antrun-plugin.version}</version>
  <executions>
    <execution>
    <phase>validate</phase>
    <goals>
      <goal>run</goal>
    </goals>
    <configuration>
      <tasks>
        <echoproperties />
      </tasks>
    </configuration>
    </execution>
  </executions>
</plugin>

Here's a taste of what it looks like:

russ@nargothrond ~/dev/project $ mvn validate
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] project                                                            [pom]
[INFO] processor                                                          [jar]
[INFO] project-nar                                                        [nar]
[INFO]
[INFO] ----------------< com.windofkeltia.project >----------------------------
[INFO] Building project 1.0.0                                             [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-antrun-plugin:1.8:run (default) @ project ---
[WARNING] Parameter tasks is deprecated, use target instead
[INFO] Executing tasks

main:
[echoproperties] #Ant properties
[echoproperties] #Wed Feb 19 10:03:24 MST 2020
[echoproperties] ant.core.lib=/home/russ/.m2/repository/org/apache/ant/ant/1.9.4/ant-1.9.4.jar
[echoproperties] ant.file=/home/russ/dev/project/pom.xml
[echoproperties]
ant.file.maven-antrun-=/home/russ/dev/project/target/antrun/build-main.xml
[echoproperties] ant.file.type.maven-antrun-=file
[echoproperties] ant.java.version=1.8
[echoproperties] ant.project.default-target=main
[echoproperties] ant.project.name=maven-antrun-
[echoproperties] ant.version=Apache Ant(TM) version 1.9.4 compiled on April 29 2014
[echoproperties] asm\:asm\:jar=/home/russ/.m2/repository/asm/asm/3.3.1/asm-3.3.1.jar
[echoproperties] awt.toolkit=sun.awt.X11.XToolkit
[echoproperties] basedir=/home/russ/dev/project
.
.
.
[echoproperties] os.arch=amd64
[echoproperties] os.name=Linux
[echoproperties] os.version=4.15.0-74-generic
[echoproperties] path.separator=\:
[echoproperties] project.artifactId=project
[echoproperties] project.build.directory=/home/russ/dev/project/target
.
.
.
[echoproperties] targeted.build.arch=linux_x86-64
[echoproperties] user.country=US
[echoproperties] user.dir=/home/russ/dev/project
[echoproperties] user.home=/home/russ
[echoproperties] user.language=en
[echoproperties] user.name=russ
[echoproperties] user.timezone=America/Denver
[echoproperties] versions-maven-plugin.version=2.7
[INFO] Executed tasks
[INFO]
[INFO] ----------------< com.windofkeltia.project >----------------------------
[INFO] Building processor 1.0.0                                           [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-antrun-plugin:1.8:run (default) @ processor ---
[WARNING] Parameter tasks is deprecated, use target instead
[INFO] Executing tasks
.
.
.

For example, you could do this:

russ@nargothrond ~/dev/project $ mvn validate
[echoproperties] project.build.directory=/home/russ/sandboxes/project/target
[echoproperties] project.build.directory=/home/russ/sandboxes/project/processor/target

A shorter version that doesn't run everything else is:


  <!-- How to see properties (values for macros, variables... -->
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.8</version>
  <executions>
    <execution>
      <id>get-properties</id>
      <phase>validate</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <tasks>
          <!-- show values of macros/variables... -->
          <echoproperties/>
        </tasks>
      </configuration>
    </execution>
  </executions>
</plugin>


$ mvn antrun:run@get-properties

Sharing code from the test aspect/scope of one module in multimodule project
project
├── pom.xml
├── application
│   └── pom.xml
└── shared
    └── pom.xml

Googling around this morning, I finally solved a problem that has plagued my multimodule projects forever: I need to develop and maintain my eternal TestUtilities class in one place (and project), for example,

project/shared/src/test/java/com/windofkeltia/utilities/TestUtilities.java

Yet, I need to be able to consume it from tests written not only in the shared project, but others as well, let's say application,

project/application/src/test/java/com/windofkeltia/SomeTest.java

application's production code already depends upon shared's production code, but, until this solution, there's been no way for appliction's test code to depend upon shared's test code.

Here's how to remedy the problem. This is real—not a hack.

First, add the following lines to share's pom.xml. Under <build>, find the plug-in named, maven-jar-plugin, because we're going to force the generation of a JAR. Here's the whole maven-jar-plugin after editing. Note: you will still only have a single instance of maven-jar-plugin after this edit, which only adds an execution clause to what other execution clauses are alread there (highlighted here):

shared/pom.xml:
  <modelVersion>4.0.0</modelVersion>
  <artifactId>shared</artifactId>
  <packaging>jar</packaging>

  <parent>
    <groupId>com.windofkeltia</groupId>
    <artifactId>project</artifactId>
    <version>1.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>
  .
  .
  .
  <build>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>2.3.1</version>
      <executions>
        <execution>
          <id>Generate a test JAR for sharing test utilities</id>
          <phase>package</phase>
          <goals>
            <goal>test-jar</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    ...
  </build>
</project>

Next, let's tackle what is a sibling module to shared in the greater project, named application. This module's test code must access the code from shared's aspect. We'll add, beside the dependency for shared's production code (first dependency, likely already there), another for its test code. Add the highlighted lines to application's pom.xml dependencies:

application/pom.xml:
  <modelVersion>4.0.0</modelVersion>
  <artifactId>application</artifactId>
  <packaging>jar</packaging>

  <parent>
    <groupId>com.windofkeltia</groupId>
    <artifactId>project</artifactId>
    <version>1.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>
  .
  .
  .
  <dependencies>
    <dependency>
      <groupId>com.windofkeltia.project</groupId>
      <artifactId>shared</artifactId>
      <version>${shared.version}</version>
    </dependency>
    <dependency>
      <groupId>com.windofkeltia.project</groupId>
      <artifactId>shared</artifactId>
      <version>${shared.version}</version>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>
    ...
  </dependencies>
  ...
  </build>
</project>

From this point on, test code in application should be able to access the TestUtilities class I have in shared from application's test code.

A note on the groupId above: in this project, all the submodules inherit the parent's (project) groupId, com.windofkeltia. Not all multimodule projects may be built so; mine tend to be. (Your mileage may vary.)


Renaming modules especially in a multimodule project

This can be done. In IntelliJ IDEA, right-click the module in the Project pane and choose Refactor → Rename, then click Rename module and directory.

It's probably best not to allow the IDE to look for occurrences in comments and strings because it will only mess up coincidental conflicts. It's better to detect and fix these by hand.

Open the newly renamed modules's pom.xml and fix its <artifactId>.

Open the root-level pom.xml and hand-fix the name of the <module> you renamed.

Think of any other Maven references to the newly renamed module and fix them.

Rebuild doing this to force Maven to think harder about repository implications:

$ mvn clean -U package

Different profiles in Maven
per project in pom.xml
per user in ~/.m2/settings.xml
global in ${MAVEN_HOME}/conf/settings.xml

How to trigger a profile during a build?

Profiles are defined in pom.xml. Triggering a profile during a build (Maven invocation) is done...

  1. from the command line,
    $ mvn -P build-jar,build-app
    
    where build-jar and build-app are different <profiles>.

  2. through Maven settings
    ~/.m2/settings.xml:
    <settings>
      <activeProfiles>
        <activeProfile>build-jar</activeProfile>
      </activeProfiles>
    </settings>
    
  3. based on environment variables,
    $ export ENVIRONMENT="debug"
    
    <profiles>
      <profile>
        <activation>
          <property>
            <name>env.ENVIRONMENT</name>
            <value>debug</value>
          </property>
        </activation>
      </profile>
    </profiles>
    

    or

    $ mvn -DENVIRONMENT=debug
    
    <profiles>
      <profile>
        <activation>
          <property>
            <name>ENVIRONMENT</name>
            <value>debug</value>
          </property>
        </activation>
      </profile>
    </profiles>
    
  4. OS settings (use $ mvn enforcer:display-info to get these; here, I'm running an up-to-date Linux Mint 20.1)
    
      
        
          
            linux
            unix
            amd64
            5.4.0-73
          
        
        ...
      
    
    
  5. present or missing files.
    <profiles>
      <profile>
        <activation>
          <file>
            <missing>target/my-jar.jar</missing>
          </file>
        </activation>
      </profile>
    </profiles>
    

Simplest way to achieve the building of an executable JAR using Maven

Java is 25 years old. This process has been done 9 ways from Sunday over that time. Most of the examples you'll find are confusing, outdated or will simply and mysteriously not work. In 2020, using the latest versions, here is what I am doing that works. I hope when you try this that it will just work for you.

What's missing from this tutorial? I pretend that MANIFEST.mf is of no value and I don't document it fully nor how to use that instead of the maven-assembly-plugin configuration that dispenses with it. I will give you some tips, however.

First, let's get some fundamentals out of the way:

  1. Use this command to build your application from the command line:
    ~/dev/project $ mvn clean package
    
  2. In your pom.xml header, you must have <packaging> set to "jar". You must also have a groupId (probably just your main package name), artifactId (probably just your project's name) and a version (doesn't have to contain "SNAPSHOT," but versioning is a different tutorial).
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.windofkeltia</groupId>
    <artifactId>project</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
  3. Unless you set these in <properties>:
    <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
    <properties>
    
    you will get complaints out of Maven when you build:
    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.2:compile \
            (default-compile) on project mds-extract: Compilation failure: Compilation failure:
    [ERROR] Source option 5 is no longer supported. Use 6 or later.
    [ERROR] Target option 1.5 is no longer supported. Use 1.6 or later.
    
  4. You do not need (contrary to advice much written out there)...
    • to use the maven-compiler-plugin in pom.xml,
    • to use the maven-jar-plugin in pom.xml,
    • to maintain lines such as these in <build>:
      <build>
        <sourceDirectory>src/main/java</sourceDirectory>
        <testSourceDirectory>src/test/java</testSourceDirectory>
        <resources>
          <resource>
            <directory>src</directory>
            <excludes>
              <exclude>**/*.java</exclude>
            </excludes>
          </resource>
        </resources>
        ...
      
    • or a manifest file such as this unless you are really serious about marking up your application with official stuff like a description, copyright and licensing information, etc. This is how the manifest file is named and where it goes if you decide to use one:
      src/main/resources/META-INF/MANIFEST.mf:
      Main-Class: com.windofkeltia.ProjectMain
      (all kinds of other labels and values; IntelliJ IDEA will help you)
      (make certain you have a blank line at the end of this file)
      
    • but you will use the maven-assembly-plugin in pom.xml!

Here's the project

My purpose was to reduce a real, working executable JAR from the smallest possible amount of code and its build file. This is the bare minimum; the result is a JAR that you can execute. Let's start with some context so that you know where everything starts and the JAR lands:

~/dev/project $ ll
drwxrwxr-x  5 russ russ 4096 Oct 22 18:55 .
drwxrwxr-x 52 russ russ 4096 Oct 22 15:34 ..
drwxrwxr-x  4 russ russ 4096 Oct 22 18:55 .idea          (IntelliJ IDEA file)
-rw-rw-r--  1 russ russ 1389 Oct 22 17:33 project.iml    (IntelliJ IDEA file)
-rw-r--r--  1 russ russ 1954 Oct 22 18:55 pom.xml
drwxrwxr-x  4 russ russ 4096 Oct 22 15:35 src
drwxrwxr-x  9 russ russ 4096 Oct 22 18:49 target
~/dev/project $ ll ./target
total 48
drwxrwxr-x 9 russ russ  4096 Oct 22 18:49 .
drwxrwxr-x 5 russ russ  4096 Oct 22 18:55 ..
drwxrwxr-x 3 russ russ  4096 Oct 22 18:49 classes
drwxrwxr-x 3 russ russ  4096 Oct 22 18:49 generated-sources
drwxrwxr-x 3 russ russ  4096 Oct 22 18:49 generated-test-sources
drwxrwxr-x 2 russ russ  4096 Oct 22 18:49 maven-archiver
drwxrwxr-x 3 russ russ  4096 Oct 22 18:49 maven-status
-rw-rw-r-- 1 russ russ 11357 Oct 22 18:49 project-1.0.0-SNAPSHOT.jar
drwxrwxr-x 2 russ russ  4096 Oct 22 18:49 surefire-reports
drwxrwxr-x 4 russ russ  4096 Oct 22 18:49 test-classes
~/dev/project $ which java
/usr/bin/java
~/dev/project $ java -jar ./target/project-1.0.0-SNAPSHOT.jar arguments
src/main/java/com/windofkeltia/ProjectMain.java:

This is your production (as opposed to test-) class that contains public void main( String[] args ):

package com.windofkeltia;

public class ProjectMain
{
  public static void main( String[] args )
  {
    String pathname = null;

    if( args.length > 0 )
      pathname = args[ 0 ];

    if( isNull( pathname ) )
    {
      System.err.println( "No document was specified as input." );
      return;
    }

    // whatever else your application wants to do: this tutorial isn't about that
  }
}
src/test/java/com/windofkeltia/ProjectMainTest.java:

You'll write a JUnit test suite for your application in files like this:

package com.windofkeltia;

public class ProjectMainTest
{
  @Test
  public void test()
  {
    String[] args = { "this", "is", "a", "test" );

    ProjectMain.main( args );
  }
}

Running and output...

If I neglect to pass a first argument, which I don't in the unit test, but let's pretend that, from the command line, I do forget, I will get something on stderr (see code in main()):

~/dev/project $ java -jar ./target/project-1.0.0-SNAPSHOT.jar arguments
No document specified as input.

(I think I have been thorough enough so that the idea of a project with a Java main() that does something at the command line is no longer confusing. And, I resisted using the greeting, "Hello World," anywhere. Doh! Except just now.)

The magic hat!

pom.xml:

Here's the Maven build file. You only need the maven-assembly-plugin to produce the executable JAR including to ensure third-party dependencies get linked into the final JAR. This Maven file is the rock-bottom simplest which will produce your application as an executable JAR. Telling Maven what the class that contains main() is of crucial importance.

<project xmlns="http://maven.apache.org/POM/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.windofkeltia</groupId>
  <artifactId>project</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <junit.version>4.12</junit.version>
    <maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- other dependencies as you need: this tutorial isn't about those -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <!-- Ensure third-party JARs get into the executable JAR -->
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>${maven-assembly-plugin.version}</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>com.windofkeltia.ProjectMain</mainClass>
                </manifest>
              </archive>
              <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
              <appendAssemblyId>false</appendAssemblyId>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Nota bene: If you do not include <appendAssemblyId>false</appendAssemblyId> in the configuration of maven-assembly-plugin, then you'll get two JARs:

  1. project-1.0.0-SNAPSHOT.jar
  2. project-1.0.0-SNAPSHOT-jar-with-dependencies.jar

The second one, much bigger, will contain whatever third-party JARs you've included in the creation of your application. You probably do not need both; this configuration setting will leave you with the second one, but under the first one's name.


How to build a JAR or other artifact without a version?

This isn't really possible, but it's possible to change the name of the artifact through the <finalName> element under the pom.xml's <build> paragraph:

<project>
  ...
  <artifactId>foo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  ...
  <build>
    ...
    <finalName>${project.artifactId}</finalName>
    ...
  </build>
</project>

The result will be foo.jar instead of foo-1.0.0.jar.


Install created JAR in local repository with maven-install-plugin

It's possible to add an artifact, in this case one I have build from my own source code, into the local, Maven repository (at ~/.m2/repository). It's easier than you might think.

All that must be done is to build using

$ mvn install

This results in the JAR finding its way to the local, Maven repository.


Generate JAR with source code inside
<build>
  ...
  <plugins>
    ...
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-source-plugin</artifactId>
      <version>${maven-source-plugin.version}</version>
      <executions>
        <execution>
          <id>attach-sources</id>
          <goals>
            <goal>jar</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    ...

The result is something like this:

$ ll target
drwxrwxr-x 12 russ russ   4096 Jun  7 15:30 .
drwxrwxr-x  5 russ russ   4096 Jun  7 15:30 ..
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 antrun
drwxrwxr-x  5 russ russ   4096 Jun  7 15:30 apidocs
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 classes
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 generated-sources
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 generated-test-sources
-rw-rw-r--  1 russ russ 154945 Jun  7 15:30 abc-1.0.0.jar
-rw-rw-r--  1 russ russ 110877 Jun  7 15:30 abc-1.0.0-sources.jar
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 javadoc-bundle-options
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 maven-archiver
-rw-rw-r--  1 russ russ   3672 Jun  7 15:30 maven-javadoc-plugin-stale-data.txt
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 maven-status
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 surefire-reports
drwxrwxr-x  4 russ russ   4096 Jun  7 15:30 test-classes

...and the install command will result in the JAR reaching ~/.m2/repository alongside the normal JAR.


Generate JAR with Javadoc

Add this to the <build> paragraph of pom.xml. If your Javadoc is crap or doesn't meet latest HTML standards and you get errors, you can shut them up using the highlighted line below. (The name of this varies slightly depending on the version of maven-javadoc-plugin.) In so far as Javadoc display, your mileage may vary by doing this.

<build>
  ...
  <plugins>
    ...
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-javadoc-plugin</artifactId>
      <version>${maven-javadoc-plugin.version}</version>
      <executions>
        <execution>
          <id>attach-javadocs</id>
          <goals>
            <goal>jar</goal>
          </goals>
          <configuration>
            <doclint>none</doclint>
          </configuration>
        </execution>
      </executions>
    </plugin>
    ...

The result is something like this:

$ ll target
drwxrwxr-x 12 russ russ   4096 Jun  7 15:30 .
drwxrwxr-x  5 russ russ   4096 Jun  7 15:30 ..
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 antrun
drwxrwxr-x  5 russ russ   4096 Jun  7 15:30 apidocs
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 classes
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 generated-sources
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 generated-test-sources
-rw-rw-r--  1 russ russ 154945 Jun  7 15:30 abc-1.0.0.jar
-rw-rw-r--  1 russ russ 920963 Jun  7 15:30 abc-1.0.0-javadoc.jar
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 javadoc-bundle-options
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 maven-archiver
-rw-rw-r--  1 russ russ   3672 Jun  7 15:30 maven-javadoc-plugin-stale-data.txt
drwxrwxr-x  3 russ russ   4096 Jun  7 15:30 maven-status
drwxrwxr-x  2 russ russ   4096 Jun  7 15:30 surefire-reports
drwxrwxr-x  4 russ russ   4096 Jun  7 15:30 test-classes

...and the install command will result in the JAR reaching ~/.m2/repository alongside the normal JAR.


How to replace installation of JAR in ~/.m2/repository

When you build

$ mvn install

The artifact built is "installed" into ~/.m2/repository (the local repository). However, from then on, changes to code will never reach the local repositor unless...

  • ...the version of the artifact is bumped or
  • ...the existing artifact in the local repository is removed:
$ rm -rf ~/.m2/repository/path to parent of artifact

Surely there is a more elegant way, but I have searched yet never found it.


How to implement an automatically incrementing build number

You may wish to version your product as major.minor.build or major.minor.revision.build, etc. For instance, perhaps you wish to create WAR files whose naming follows the pattern:

web-application##major.minor.revision-build.war

Such as (for example) my-app##3.3.4-109.war. See Naming the WAR file in Maven.

Solution

The solution isn't the most elegant approach to this (some will tell you to adopt Gradle). However, I have used this solution, first in ant (see How to manage buildnumber), then in Maven (what you're about to see). This solution's lately been touched (version 3.0.0, December 2021) and is named buildnumber-maven-plugin from Code Haus. I used it with ant over a decade ago.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>buildnumber-maven-plugin</artifactId>
  <version>3.0.0</version>
  <executions>
    <execution>
      <id>buildnumber</id>
      <phase>test</phase> <!-- See note below. -->
      <goals>
        <goal>create</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <format>{0,number}</format>
    <items>
      <item>buildNumber</item>
    </items>
    <doCheck>false</doCheck>
    <doUpdate>false</doUpdate>
    <revisionOnScmFailure>true</revisionOnScmFailure>
  </configuration>
</plugin>
<scm>
  <connection> scm:svn:http://127.0.0.1/dummy </connection>
  <developerConnection> scm:svn:https://127.0.0.1/dummy </developerConnection>
  <tag>HEAD</tag>
  <url> http://127.0.0.1/dummy </url>
</scm>

Higher up in pom.xml, you should have version defined thus:

<version>3.3.4-${buildNumber}</version>

Notice that I do not ask the plug-in to mess with my major, minor and revision versions. I don't even know if that's something the plug-in will do for me (see the plug-in's <configuration ... />); I don't care to have it do that anyway.

How does this work?

The way the auto-incrementation works is very straightforward. It requires a file, buildNumber.properties in the same subdirectory as pom.xml. After the fifth invocation of Maven, this file contains:

$ mvn clean package
  .
  .
  .
$ cat buildNumber.properties
#maven.buildNumber.plugin properties file
#Wed Dec 08 09:43:44 MST 2021
buildNumber=5

It follows, therefore, that if you wish to back up to an earlier build number, all you need do is modify the third line.

Note: Why use phase test instead of validate?

I have found that the build number in ./buildNumber.properties gets incremented even when unit tests fail if phase validate is specified. If one or more unit tests fail, I do not want the build number incremented since my next working build should be the same number as the previously deployed build plus 1 arithmentically.

Most googling on this issue stumbles upon pom.xml examples that usually show

<phase>validate</phase>

used. This is not what I want.

Since, on a very bad day, it could take me several dozen invocations of Maven before I get confirmation that all the errors are fixed, I don't want build number auto-incremented each time, but only after there are no more errors.

Even then, I might have to adjust the build number by editing ./buildNumber.properties, but asking the plug-in to way until after successfully running the JUnit tests usually works as I want it to.


Life cycles, goals and phases

The Maven life cycle

Each Maven life cycle consists of a sequence of phases. For instance, the default build life cycle, which is Maven's main build life cycle, consists of 23 phases.

The clean life cycle consists of 3 phases, the site lifecycle of 4 phases.

Here are the 3, built-in life cycles:

  1. default —the main life cycle as is responsible for project deployment.
  2. clean —removes all generated (i.e.: non-source) files from the previous build(s).
  3. site —creates the project's site documentation.

I believe that the name of the life cycle never appears on the Maven command line.

Or, almost never. It's unclear to me whether clean is (also) a goal or only a life cycle. (I think it's both and, when used on the command line, indicates a goal.)

The life cycle consistently appears, however, in plug-in configuration—usually in <executions>, something like this:

<executions>
  <execution>
    <id>default</id>
    <goals>
      <goal>build</goal>
    </goals>
  </execution>
</executions>

The phase in the Maven life cycle

Each Maven phase represents a stage in the build life cycle. Each phase is responsible for a specific task.

  1. validate —that all information essential to the build is present.
  2. compile —the source code.
  3. test-compile —ditto for the test code.
  4. test —run the unit tests.
  5. package —integrate the source binaries into a distributable package such as a JAR, WAR, NAR, etc.
  6. integration-test —deploy if need-be to run integration tests.
  7. install —the package in a local repository.
  8. deploy —copy the package to a remote repository.
$ mvn phase

..., for instance, will cause the requested phase to run, but only after the other phases on which it is dependent have been run. For example, deploy, the last phase of the default build life cycle, will entail the entire default life cycle's phases to be run.

The phase appears most frequently on the Maven command line.

The goal in the phase

Goals are made up by plug-in writers. Plug-in configuration is correspondingly confusing and chaotic using <phase>, <goal>, <id> and other terminology, often in error. For example, Code Haus authors think build a phase while Spotify developers think it's goal.

Each phase consists of a sequence of one or more goals, each of which is responsible for a specific task. When a phase is run (as deploy in the example above), all goals bound to this phase are executed in a prescribed (and natural) order.

For instance, compiler:compile. The compile goal from a compiler plug-in like org.apache.maven.plugins.maven-compiler-plugin, is bound to the compile phase.

The test goal, of surefire:test, may be bound to the test phase. Note that phases and goals may be named identically. It's up to your skill and familiarity with Maven to discern between them (yeah, to hell with that, right?).

The jar goal, of jar:jar, or the war goal of war:war, is bound to the package phase.

The goal appears occasionally on the Maven command line, usually after a plug-in name and separated from it by a colon (:).

For example, Mavin plug-ins...

...allow for the specification of (a group of) goals. Confusingly (and importantly), these goals aren't always bound to the same phase. This is probably due to there never being, in the mind of the plug-in's author perhaps, any ambiguity because of the plug-in's obvious semantics. Here's a simple configuration of the maven-failsafe-plugin created to aid in running integration tests:

<plugin>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${maven.failsafe.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
</plugin>

This plug-in, configured as shown, specifies two main goals, integration-test, to run integration tests, and verify, to ensure that all integration tests pass (before releasing Maven execution as being successful).

Note that in more complex Maven commands, you will tend to see a plug-in specified, when specified at all, suffixed by a colon (:). Find an example below of the maven-javadoc-plugin. You will note that what appears on the command line is the name of plugin without prefix maven- and without the suffix -plugin. It will always be followed by a goal name.

Maven commands illustrating the above

to list all goals for a given plug-in, do this:

$ mvn plugin-name:help

To build a Maven project, simply execute the intended life cycle by running one of the phases:

$ mvn deploy             // the whole enchilada
$ mvn install            // stop at the install phase
$ mvn clean package      // build only the JAR, WAR, NAR, etc.
$ mvn compiler:compile   // just see if stuff compiles
$ mvn compile            // (ditto)
$ mvn install javadoc:javadoc javadoc:aggregate
                         // build and install Javadoc locally

Appendix: Problems to sort out and make examples of

In multimodule projects, such as the need to build a custom NAR for Apache NiFi, and to bundle that NAR in a Docker container, (pom.xml) module names must be twisted in order for the NAR to have been fully built before the Spotify Docker plug-in is unleashed on it because to do so too early results in

[ERROR] ADD failed: no source files were specified
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Custom NAR 4.1.0:
[INFO]
[INFO] Custom NAR ......................................... FAILURE [02:56 min]
[INFO] shared ............................................. SKIPPED
[INFO] custom ............................................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:57 min
[INFO] Finished at: 2022-01-28T09:50:06-07:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.spotify:dockerfile-maven-plugin:1.4.13:build (default) on project custom-parent: \
    Could not build image: ADD failed: no source files were specified
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal com.spotify:dockerfile-maven-plugin:1.4.13:build ...

The NAR file hasn't been created yet, so isn't in nar/target/custom-nar-4.1.0.nar. Goal build clearly isn't the right one to specify in the Spotify plug-in.

Trying separately:

$ mvn clean package     // builds the custom NAR
$ mvn dockerfile:build  // invokes the Spotify plug-in afterward

The problem is that the second invocation wants to walk all the modules all over again. It fails to find a Dockerfile in the modules. Of course, I don't need it to walk the submodules. The product of all of those is already encased in the NAR file. I just need to copy the NAR file into an Apache NiFi Docker container.

[ERROR] Missing Dockerfile in context directory: /home/russ/sandboxes/nifi-pipeline/shared
.
.
.
[ERROR] Failed to execute goal com.spotify:dockerfile-maven-plugin:1.4.13:build (default-cli) on project shared: \
    Missing Dockerfile in context directory: /home/russ/sandboxes/nifi-pipeline/shared

It's ugly, but skipping modules when building the Docker container appears to work; both these command lines use the -pl option to skip the modules. Obviously the second one is better and works since Maven knows how to determine the submodules in the current subdirectory (.) and we don't have to list them by hand.

$ mvn -pl '!shared, !custom' dockerfile:build
$ mvn -pl . dockerfile:build

Putting source code and class files into a JAR

First, realize that, despite using make-repo.sh to create a Maven filesystem in the local repository in lib off the project root, Maven wants to use ~/.m2/repository to build with. In order to get the JARs from lib to ~/.m2/repository, use this command:

$ mvn clean -U package

Do not use the example suggested by Generate source code JAR for Maven-based project. This will only create two JARs, both with only source code inside. The link errors of missing symbols will make you pull your head out until you realize this.

Instead, do this:

  1. In addition to your proper configuration of maven-compiler-plugin for generating your JAR containing class files:
          
            org.apache.maven.plugins
            maven-compiler-plugin
            ${maven-compiler-plugin.version}
            
              1.8
              1.8
            
          
    
  2. Also include these lines below <build> and before <plugins>:
        
          
            src/main/resources
          
          
            src/main/java
            
              **/*.java
            
          
        
    
  3. The above assumes...
    • ...you want to ensure that everything under src/main/resources survives into your JAR;
    • ...your source code will survive as .java alongside your .class files.

  4. This should function in any IDE when it comes to it finding the source code to display as you edit, debug, etc. the consuming code of your project. You will notice, however, that your careful code format will not survive as your IDE reformats it for display.

  5. Now, it might not be the best idea to distribute the Java source code when it's the company cookie jar (no pun intended). There are two choices:
    • Comment out the "resource" lines in pom.xml's "build" section, or...
    • do follow Yong's article (linked above), figure out how it's wrong and fix it. This would result in two, separate JARs, the second one named (very obviously) base-library-4.1-sources.jar, i.e.: the one you don't distribute to customers.

  6. Here's the source to make-repo.sh:
    #!/bin/sh
    #-------------------------------------------------------------------------------
    # From a JAR file, create a Maven repository filesystem in imitation of what's
    # on ~/.m2/repository.
    #-------------------------------------------------------------------------------
    if [ -n "$1" ]; then
      if [ $1 = "--help" -o $1 = "-h" ]; then
        echo "Create local artifactory repository, arguments:"
        echo "  1: path to repository subdirectory"
        echo "  2: path to JAR"
        echo "  3: groupId"
        echo "  4: artifactId"
        echo "  5: version"
        exit 0
      fi
    fi
    
            TARGET_REPO=$1
           JAR_FILENAME=$2
                GROUPID=$3
             ARTIFACTID=$4
                VERSION=$5
    
    HARDCODED_ARGUMENTS="-DcreateChecksum=true \
                         -Dpackaging=jar \
                         -DgeneratePom=true"
         SOFT_ARGUMENTS="-DlocalRepositoryPath=$TARGET_REPO \
                         -Dfile=$JAR_FILENAME \
                         -DgroupId=$GROUPID \
                         -DartifactId=$ARTIFACTID \
                         -Dversion=$VERSION"
    
    #echo "$HARDCODED_ARGUMENTS $SOFT_ARGUMENTS"
    
    errors=
    
    if [ -z "$TARGET_REPO" ]; then
      echo "Missing path to target repository"
      errors=1
    fi
    if [ -z "$JAR_FILENAME" ]; then
      echo "Missing path to JAR"
      errors=2
    fi
    if [ -z "$GROUPID" ]; then
      echo "Missing JAR's groupId"
      errors=3
    fi
    if [ -z "$ARTIFACTID" ]; then
      echo "Missing JAR's artifactId"
      errors=4
    fi
    if [ -z "$VERSION" ]; then
      echo "Missing JAR version"
      errors=5
    fi
    
    if [ $errors -gt 0 ]; then
      exit 1
    fi
    
    if [ -n "$SOFT_ARGUMENTS" ]; then
      mvn -e install:install-file $HARDCODED_ARGUMENTS $SOFT_ARGUMENTS
    fi
    
    # vim: set tabstop=2 shiftwidth=2 noexpandtab:
    

Maintaining multiple consumed libraries...

...containing not only class files (compiled Java code), but also sources to those class files.

This is more about IntelliJ IDEA perhaps than about Maven. You should assimilate the information in the immediately previous note on source-code in JARs before tackling what follows here.

Let's imagine you have three private library JARs and an application. Each is in its own IDEA (and Maven) project, but, for our "trick" here, I'm adding srclib.*

  1. base-library
  2. utilities-library —depends upon base-library
  3. business-logic —depends upon base-library and utilities-library
  4. application —depends upon base-library, utilities-library and business-logic

Each project has a filesystem containing about what you'd expect for a proper Maven project in IntelliJ IDEA, principally (because we're only interested in subdirectories here):

~/sandboxes $ tree
base-library
├── lib
├── src
├── srclib
└── update-consumers.sh  # copies base-library.jar to
                                  utilities-library/srclib,
                                  business-logic/srclib and
                                  application/srclib
utilities-library
├── lib
├── src
├── srclib               # acquires base-library.jar
└── update-consumers.sh  # copies utilities-library.jar to
                                  business-logic/srclib and
                                  application/srclib
business-logic
├── lib
├── src
├── srclib               # acquires base-library.jar and utilities-library.jar
└── update-consumers.sh  # copies business-logic.jar to
                                  application/srclib
application
├── lib
├── src
└── srclib               # acquires base-library.jar, utilities-library.jar and business-logic.jar

In order for utilities-library, business-logic and application to build, the libraries upon which they depend must find their way into ~/.m2/repository.

Here's how to get base-library into utilities-library as well as into ~/.m2/repository, which is essential for displaying changed code in the IDE (debugger, etc.). This will have to happen for business-logic and, later, nifi-pipeline too.

  1. Go to base-library and execute update-consumers.sh. This ensures that base-library-4.1.jar reach utilities-library/srclib and business-logic/srclib.

  2. Go to utilities-library/srclib and execute make-repo.sh (from previous note on this page):
    $ make-repo.sh ../lib ./base-library-4.1.jar com.windofkeltia.base-library base-library 4.1
    
    This ensures the following in utilities-library/lib:
    ~/sandboxes/utilities-library $ tree lib/com/windofkeltia/base-library/
    lib/com/windofkeltia/base-library/
    └── base-library
        ├── 4.1
        │   ├── base-library-4.1.jar
        │   ├── base-library-4.1.jar.md5
        │   ├── base-library-4.1.jar.sha1
        │   ├── base-library-4.1.pom
        │   ├── base-library-4.1.pom.md5
        │   └── base-library-4.1.pom.sha1
        ├── maven-metadata-local.xml
        ├── maven-metadata-local.xml.md5
        └── maven-metadata-local.xml.sha1
    
    2 directories, 9 files
    
  3. Now go erase this library from the local Maven repository...
    
    ~/.m2/repository/com/windofkeltia $ ll
    total 16
    drwxrwxr-x  4 russ russ 4096 Feb 22 12:08 .
    drwxrwxr-x 55 russ russ 4096 Feb 21 15:04 ..
    drwxrwxr-x  3 russ russ 4096 Feb 22 12:08 base-library
    ~/.m2/repository/com/windofkeltia $ rm -rf base-library
    
    ...because base-library.jar is gone from the local Maven repository, the next step will restore it, but using the latest build of that library. This would probably not happen if the library's checksum remained the same.

  4. Execute Maven in utilities-library project root thus:
    $ mvn dependency:resolve -U
    
    This Maven command will cause base-library-4.1.jar to be updated in the local Maven repository on ~/.m2/repository.

  5. Go to business-logic/srclib and perform the same steps for this project as were done for utilities-library.

  6. There's no need to go application/srclib and perform these steps since now business-logic.jar is already in ~/.m2/repository.

Notes

* I'm not only adding srclib for the purposes exposed in this note, but also as a place to hold one or more third-party JARs that aren't in Maven Central (or any other Maven contributing repositories) that I need for linking purposes. To read up on how I do this, consider Local-to-project Maven repository and about the next 5 notes following it.