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.
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
<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.
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.
<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.