Create a Java CLI application with Maven

Create a Java CLI application with Maven

Creating a CLI application is part of our daily routine. In java it is strait forward; all you need to do is to add a class with the main function:
public static void main(String[] args) {
}
This is nice for debugging from eclipse, but if you need to release this jar with the cli working, you will face the following issues:
1.       You need to add the main class to you manifest file and compile it in the jar.
2.       You need to create a folder with all the jar’s dependencies needed to run this cli.

Main Class

maven-jar-plugin

The first issue can be solved by using the maven-jar-plugin:
<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-jar-plugin</artifactId>
       <configuration>
              <archive>
                     <manifest>
                           <addClasspath>true</addClasspath>
                           <mainClass>com.package.Main</mainClass>
                     </manifest>
              </archive>
              <finalName>jarname-cli</finalName>
       </configuration>
</plugin>
 
This plugin will create a jar file by the name of jarname-cli.jar, will a manifest file with the following entry:
Main-Class: com.package.Main
This will allow us to run from the command line:
java -jar jarname-cli %1 %2.

Jar Dependencies

Though if your jar has dependencies to other jars you will receive the following error when running the command line:
Exception in thread "main" java.lang.NoClassDefFoundError:
This is because we still need to package all the dependency jars into the same folder.
To solve this you need two more plugins.

maven-dependency-plugin (http://maven.apache.org/plugins/maven-dependency-plugin/)

The first plugin will calculate all the dependencies and copy them to a directory (cli):
<plugin>
       <artifactId>maven-dependency-plugin</artifactId>
       <executions>
              <execution>
                     <phase>install</phase>
                     <goals>
                           <goal>copy-dependencies</goal>
                     </goals>
                     <configuration>
       <outputDirectory>${project.build.directory}/../cli/</outputDirectory>
                     </configuration>
              </execution>
       </executions>
</plugin>
 
This plugin will copy all dependencies even though you don’t need all of them (it will include testing and jars not necessarily in use). You can start to manage you dependencies with the exclude, but this way you will constantly need to check that new jars are not added. The other option is to tell the plugin only the jars you want added:
                     <configuration>
                           <includeScope>runtime</includeScope>
                           <includeArtifactIds>commons-cli,spring-core,commons-logging,freemarker</includeArtifactIds>
                           <outputDirectory>${project.build.directory}/../cli/</outputDirectory>
                     </configuration>
 
This way any new dependencies will not be copied to the directory.
Of course if there are dependencies that you need for the execution of your cli, you will need to add them. You can use either the includeArtifactIds, or includeGroupIds whichever is easier.
This plugin is nice since it will get all the dependencies, but what it does not do is copy current jar to your cli directory. For this you need the ant plugin to copy the current jar

maven-antrun-plugin (http://maven.apache.org/plugins/maven-antrun-plugin/)

<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-antrun-plugin</artifactId>
       <executions>
              <execution>
                     <id>Copy Main Jar To Cli Directory</id>
                     <phase>install</phase>
                     <configuration>
                           <tasks>
                                  <copy todir="${project.build.directory}/../cli/">
                                         <fileset dir="${project.build.directory}/" includes="*.jar"
                                                excludes="*sources*.jar,*javadoc*.jar" />
                                  </copy>
                           </tasks>
                     </configuration>
                     <goals>
                           <goal>run</goal>
                     </goals>
              </execution>
       </executions>
</plugin>
 
The action is very simple, it is a copy action from ant, that will copy the jar from the target directory.

maven-clean-plugin (http://maven.apache.org/plugins/maven-clean-plugin/)

Since your maven project will create the dir and all the jars needed, you need to add the clean plugin to remove the dir when running clean in maven.
<plugin>
       <artifactId>maven-clean-plugin</artifactId>
       <configuration>
              <filesets>
                     <fileset>
                           <directory>${project.build.directory}/../cli</directory>
                     </fileset>
              </filesets>
       </configuration>
</plugin>
 
This solution is good for most cases. But sometimes you need more flexibility. Occasionally you might want not to have a directory with all jar’s but you might want to pack them all in one jar (thought licensing must be considered). Also sometimes you might want to add more files or resources to the jar, or maybe create a zip file instead of the jar file. For this you have the assembly plugin. The dependency plugin is actually a subset of the assembly plugin, so you can do all the above and more.

maven-assembly-plugin (http://maven.apache.org/plugins/maven-assembly-plugin/)

We will not go into the whole plugin since you can do a lot with it (for a full set of options see http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html).
                     <plugin>
                           <artifactId>maven-assembly-plugin</artifactId>
                           <configuration>
                                  <finalName>wsf_${version}</finalName>
                                  <appendAssemblyId>false</appendAssemblyId>
                                  <descriptors>
                                         <descriptor>src/main/resources/assembly.xml</descriptor>
                                  </descriptors>
                                  <outputDirectory>${project.build.directory}/../assembly/</outputDirectory>
                           </configuration>
                           <executions>
                                  <execution>
                                         <id>make-assembly</id>
                                         <!-- this is used for inheritance merges -->
                                         <phase>package</phase>
                                         <!-- bind to the packaging phase -->
                                         <goals>
                                                <goal>single</goal>
                                         </goals>
                                  </execution>
                           </executions>
                     </plugin>
 
As you can see we can do both creating a manifest file with the mainclass, and adding other files. You can enable enhanced features by assigning an assembly.xml and do for example:
 
<assembly>
        <id>bundle</id>
        <formats>
               <format>jar</format>
        </formats>
        <includeBaseDirectory>false</includeBaseDirectory>
        <dependencySets>
               <dependencySet>
                       <outputDirectory>/</outputDirectory>
                       <useProjectArtifact>true</useProjectArtifact>
                       <unpack>true</unpack>
                       <scope>runtime</scope>
                       <includes>
                               <include>*:commons-cli:*</include>
                               <include>*:spring-core:*</include>
                               <include>*:commons-logging:*</include>
                               <include>*:freemarker:*</include>
                       </includes>
               </dependencySet>
        </dependencySets>
</assembly>
 

 

Here you can notice that we used the option <unpack>true</unpack>. This will unpack all jar’s and then repack them all in one jar. This way you can bundle you cli into one jar only.

 

Backend/Data Architect

Backend Group
Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com